@tostudy-ai/cli 0.7.3 → 0.8.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 +668 -363
- package/dist/cli.js.map +4 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1340,6 +1340,26 @@ async function saveCourseLearnerProfile(course, learnerProfile, artifacts, confi
|
|
|
1340
1340
|
},
|
|
1341
1341
|
configDir
|
|
1342
1342
|
);
|
|
1343
|
+
await saveUserProfile(learnerProfile, configDir);
|
|
1344
|
+
}
|
|
1345
|
+
function getUserProfilePath(configDir) {
|
|
1346
|
+
return path2.join(getConfigDir(configDir), "user-profile.json");
|
|
1347
|
+
}
|
|
1348
|
+
async function getUserProfile(configDir) {
|
|
1349
|
+
const profilePath = getUserProfilePath(configDir);
|
|
1350
|
+
if (!fs2.existsSync(profilePath)) return null;
|
|
1351
|
+
try {
|
|
1352
|
+
return JSON.parse(fs2.readFileSync(profilePath, "utf-8"));
|
|
1353
|
+
} catch {
|
|
1354
|
+
return null;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
async function saveUserProfile(profile, configDir) {
|
|
1358
|
+
const dir = getConfigDir(configDir);
|
|
1359
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1360
|
+
fs2.writeFileSync(getUserProfilePath(configDir), JSON.stringify(profile, null, 2), {
|
|
1361
|
+
mode: 384
|
|
1362
|
+
});
|
|
1343
1363
|
}
|
|
1344
1364
|
async function saveSession(session, configDir) {
|
|
1345
1365
|
const dir = getConfigDir(configDir);
|
|
@@ -2052,7 +2072,7 @@ var init_login = __esm({
|
|
|
2052
2072
|
const data = createHttpProvider(apiUrl, token2);
|
|
2053
2073
|
const courses3 = await listCourses({ userId }, { data, logger: logger2 });
|
|
2054
2074
|
if (courses3.length > 0) {
|
|
2055
|
-
console.log(` \u2713 ${courses3.length} curso(s)
|
|
2075
|
+
console.log(` \u2713 ${courses3.length} curso(s) matriculado(s)`);
|
|
2056
2076
|
const activeCourse = await getActiveCourse();
|
|
2057
2077
|
const targetCourse = activeCourse && courses3.find((c) => c.courseId === activeCourse.courseId) ? activeCourse : null;
|
|
2058
2078
|
if (!targetCourse && courses3.length === 1) {
|
|
@@ -2234,7 +2254,7 @@ async function exchangeCliSessionForMcpToken(session, fetchImpl = fetch) {
|
|
|
2234
2254
|
async function runMcpSetup(session, token2, spawnImpl = spawn) {
|
|
2235
2255
|
const command = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
2236
2256
|
await new Promise((resolve, reject) => {
|
|
2237
|
-
const child = spawnImpl(command, ["@tostudy-ai/mcp-setup", "--url", session.apiUrl], {
|
|
2257
|
+
const child = spawnImpl(command, ["-y", "@tostudy-ai/mcp-setup", "--url", session.apiUrl], {
|
|
2238
2258
|
stdio: "inherit",
|
|
2239
2259
|
env: {
|
|
2240
2260
|
...process.env,
|
|
@@ -2260,6 +2280,7 @@ var init_mcp_setup = __esm({
|
|
|
2260
2280
|
// src/workspace/instruction-files.ts
|
|
2261
2281
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2262
2282
|
import { join as join2 } from "node:path";
|
|
2283
|
+
import os4 from "node:os";
|
|
2263
2284
|
function slugify(title) {
|
|
2264
2285
|
return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
2265
2286
|
}
|
|
@@ -2355,7 +2376,8 @@ FIM DO M\xD3DULO:
|
|
|
2355
2376
|
"**Celebre progresso** \u2014 reconhe\xE7a quando o aluno completa li\xE7\xF5es e m\xF3dulos",
|
|
2356
2377
|
"**Respeite a sequ\xEAncia** \u2014 n\xE3o pule m\xF3dulos nem li\xE7\xF5es",
|
|
2357
2378
|
"**Adapte ao n\xEDvel** \u2014 ajuste profundidade e exemplos conforme o perfil do aluno",
|
|
2358
|
-
"**Sempre rode `tostudy lesson`** antes de discutir conte\xFAdo \u2014 n\xE3o invente material"
|
|
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.'
|
|
2359
2381
|
];
|
|
2360
2382
|
if (learner?.adaptToRealContext) {
|
|
2361
2383
|
rules.push("**Use contexto real** \u2014 adapte exemplos ao projeto/empresa do aluno");
|
|
@@ -2368,24 +2390,47 @@ ${rules.map((r, i) => `${i + 1}. ${r}`).join("\n")}`);
|
|
|
2368
2390
|
| Situa\xE7\xE3o | O que fazer |
|
|
2369
2391
|
|----------|-------------|
|
|
2370
2392
|
| \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
|
|
2371
|
-
| \`tostudy start\`
|
|
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. |
|
|
2372
2394
|
| "Nenhuma li\xE7\xE3o ativa" | Rodar \`tostudy start\` para carregar m\xF3dulo |
|
|
2373
2395
|
| Comando retorna erro | Verificar \`tostudy doctor\` para diagn\xF3stico |
|
|
2374
|
-
| Aluno perdido / sem saber o que fazer | Rodar \`tostudy progress\` e resumir estado atual
|
|
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 |`);
|
|
2375
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
|
+
}
|
|
2376
2419
|
sections.push(`## Workspace
|
|
2377
2420
|
|
|
2378
|
-
|
|
2421
|
+
${locationHint}
|
|
2379
2422
|
|
|
2380
2423
|
\`\`\`
|
|
2381
|
-
|
|
2424
|
+
${displayPath}/
|
|
2382
2425
|
\u251C\u2500\u2500 exercises/{m\xF3dulo}/{li\xE7\xE3o}/ \u2190 Exerc\xEDcios extra\xEDdos
|
|
2383
2426
|
\u251C\u2500\u2500 generated/ \u2190 Artefatos gerados
|
|
2384
|
-
\
|
|
2427
|
+
\u251C\u2500\u2500 diagrams/ \u2190 Diagramas
|
|
2428
|
+
\u2514\u2500\u2500 ${vaultRelativeHint}
|
|
2385
2429
|
\`\`\`
|
|
2386
2430
|
|
|
2387
2431
|
Comandos \xFAteis:
|
|
2388
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")
|
|
2389
2434
|
- \`tostudy open\` \u2014 Abrir workspace no editor
|
|
2390
2435
|
- \`tostudy workspace status\` \u2014 Verificar estado do workspace`);
|
|
2391
2436
|
}
|
|
@@ -2471,6 +2516,8 @@ Rode \`tostudy progress --json\` para descobrir o estado atual do aluno.
|
|
|
2471
2516
|
|
|
2472
2517
|
### Se curso ativo mas sem perfil de aprendizagem
|
|
2473
2518
|
|
|
2519
|
+
**IMPORTANTE:** N\xE3o interrompa a aula para coletar o perfil. Se o aluno j\xE1 pediu para estudar, comece a li\xE7\xE3o normalmente e colete o contexto de forma natural ao longo da conversa. Rode \`tostudy init\` em background quando tiver dados suficientes.
|
|
2520
|
+
|
|
2474
2521
|
O aluno ainda n\xE3o configurou seu contexto. Colete conversacionalmente:
|
|
2475
2522
|
|
|
2476
2523
|
\`\`\`
|
|
@@ -2543,23 +2590,25 @@ FIM DO M\xD3DULO:
|
|
|
2543
2590
|
5. **Respeite a sequ\xEAncia** \u2014 n\xE3o pule m\xF3dulos nem li\xE7\xF5es
|
|
2544
2591
|
6. **Adapte ao n\xEDvel** \u2014 ajuste profundidade conforme o perfil do aluno
|
|
2545
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.
|
|
2546
2594
|
|
|
2547
2595
|
## Quando Algo D\xE1 Errado
|
|
2548
2596
|
|
|
2549
2597
|
| Situa\xE7\xE3o | O que fazer |
|
|
2550
2598
|
|----------|-------------|
|
|
2551
2599
|
| \`tostudy validate\` falhou | Mostrar feedback, sugerir \`tostudy hint\`, tentar de novo |
|
|
2552
|
-
| \`tostudy start\`
|
|
2600
|
+
| \`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. |
|
|
2553
2601
|
| "Nenhuma li\xE7\xE3o ativa" | Rodar \`tostudy start\` para carregar m\xF3dulo |
|
|
2554
2602
|
| Comando retorna erro | Verificar \`tostudy doctor\` para diagn\xF3stico |
|
|
2555
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 |
|
|
2556
2605
|
|
|
2557
2606
|
## Workspace
|
|
2558
2607
|
|
|
2559
|
-
|
|
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.
|
|
2560
2610
|
|
|
2561
|
-
- \`tostudy
|
|
2562
|
-
- \`tostudy export\` \u2014 Extrai exerc\xEDcio para o workspace
|
|
2611
|
+
- \`tostudy export\` \u2014 Extrai exerc\xEDcio para o workspace (esta pasta ou ~/study/{slug}/)
|
|
2563
2612
|
- \`tostudy open\` \u2014 Abre workspace no editor
|
|
2564
2613
|
- \`tostudy workspace status\` \u2014 Verifica estado
|
|
2565
2614
|
|
|
@@ -2708,10 +2757,7 @@ async function runSetup(opts, deps = defaultDeps) {
|
|
|
2708
2757
|
deps.log(` \u2713 ${file2}`);
|
|
2709
2758
|
}
|
|
2710
2759
|
deps.log("");
|
|
2711
|
-
|
|
2712
|
-
(ide) => ideToPlatform(ide.name) === "claude" || ideToPlatform(ide.name) === "cursor"
|
|
2713
|
-
);
|
|
2714
|
-
if (opts.mcp || hasMcpIde) {
|
|
2760
|
+
if (opts.mcp) {
|
|
2715
2761
|
deps.log(" 5. Configurando MCP...");
|
|
2716
2762
|
try {
|
|
2717
2763
|
const { token: token2 } = await deps.exchangeCliSessionForMcpToken(session);
|
|
@@ -2724,10 +2770,14 @@ async function runSetup(opts, deps = defaultDeps) {
|
|
|
2724
2770
|
const activeCourse = await deps.getActiveCourse();
|
|
2725
2771
|
const hasClaudeCmd = existsSync3(join3(cwd, ".claude", "commands", "tostudy.md"));
|
|
2726
2772
|
const hasCursorRule = existsSync3(join3(cwd, ".cursor", "rules", "tostudy.mdc"));
|
|
2773
|
+
const hasMcpConfig = existsSync3(join3(cwd, ".claude", "claude_desktop_config.json")) || existsSync3(join3(cwd, ".mcp.json"));
|
|
2774
|
+
const hasClaudeIDE = detected.some((ide) => ide.name.toLowerCase().includes("claude"));
|
|
2727
2775
|
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");
|
|
2728
2776
|
deps.log(" Setup completo!\n");
|
|
2729
2777
|
if (activeCourse) {
|
|
2730
2778
|
deps.log(` Curso ativo: ${activeCourse.courseTitle}`);
|
|
2779
|
+
} else {
|
|
2780
|
+
deps.log(" Nenhum curso ativo \u2014 rode: tostudy courses");
|
|
2731
2781
|
}
|
|
2732
2782
|
if (hasClaudeCmd) {
|
|
2733
2783
|
deps.log(" \u2192 No Claude Code, digite: /tostudy");
|
|
@@ -2736,6 +2786,9 @@ async function runSetup(opts, deps = defaultDeps) {
|
|
|
2736
2786
|
} else {
|
|
2737
2787
|
deps.log(" \u2192 Abra seu IDE e o tutor estar\xE1 dispon\xEDvel");
|
|
2738
2788
|
}
|
|
2789
|
+
if (hasClaudeIDE && !hasMcpConfig && !opts.mcp) {
|
|
2790
|
+
deps.log("\n \u{1F4A1} Dica: rode tostudy setup --mcp para habilitar ferramentas avan\xE7adas");
|
|
2791
|
+
}
|
|
2739
2792
|
deps.log("");
|
|
2740
2793
|
}
|
|
2741
2794
|
async function runSetupMcpSubcommand() {
|
|
@@ -2790,12 +2843,12 @@ __export(update_checker_exports, {
|
|
|
2790
2843
|
});
|
|
2791
2844
|
import fs4 from "node:fs";
|
|
2792
2845
|
import path4 from "node:path";
|
|
2793
|
-
import
|
|
2846
|
+
import os5 from "node:os";
|
|
2794
2847
|
function getConfigDir2() {
|
|
2795
2848
|
if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
|
|
2796
2849
|
return path4.join(process.env["XDG_CONFIG_HOME"], "tostudy");
|
|
2797
2850
|
}
|
|
2798
|
-
return path4.join(
|
|
2851
|
+
return path4.join(os5.homedir(), ".tostudy");
|
|
2799
2852
|
}
|
|
2800
2853
|
function readCache() {
|
|
2801
2854
|
try {
|
|
@@ -3021,8 +3074,112 @@ var init_courses2 = __esm({
|
|
|
3021
3074
|
}
|
|
3022
3075
|
});
|
|
3023
3076
|
|
|
3077
|
+
// src/workspace/resolve.ts
|
|
3078
|
+
import fs5 from "node:fs/promises";
|
|
3079
|
+
import path5 from "node:path";
|
|
3080
|
+
import os6 from "node:os";
|
|
3081
|
+
function courseSlug(title) {
|
|
3082
|
+
return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
3083
|
+
}
|
|
3084
|
+
async function resolveWorkspace(courseTitle, basePath = DEFAULT_BASE) {
|
|
3085
|
+
const slug = courseSlug(courseTitle);
|
|
3086
|
+
const candidate = path5.join(basePath, slug);
|
|
3087
|
+
try {
|
|
3088
|
+
await fs5.access(path5.join(candidate, ".ana-config.json"));
|
|
3089
|
+
return { found: true, workspacePath: candidate, source: "default" };
|
|
3090
|
+
} catch {
|
|
3091
|
+
return { found: false, workspacePath: null };
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
async function isCwdWorkspace(cwd = process.cwd()) {
|
|
3095
|
+
return await resolveCwdWorkspacePath(cwd) !== null;
|
|
3096
|
+
}
|
|
3097
|
+
async function resolveCwdWorkspacePath(cwd = process.cwd()) {
|
|
3098
|
+
const tostudyDir = path5.join(cwd, ".tostudy");
|
|
3099
|
+
try {
|
|
3100
|
+
const stat = await fs5.stat(tostudyDir);
|
|
3101
|
+
if (stat.isDirectory()) return tostudyDir;
|
|
3102
|
+
} catch {
|
|
3103
|
+
}
|
|
3104
|
+
try {
|
|
3105
|
+
await fs5.access(path5.join(cwd, ".ana-config.json"));
|
|
3106
|
+
return cwd;
|
|
3107
|
+
} catch {
|
|
3108
|
+
return null;
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
function resolveVaultPath(workspacePath, slug) {
|
|
3112
|
+
const base = path5.basename(workspacePath) === ".tostudy" ? path5.dirname(workspacePath) : workspacePath;
|
|
3113
|
+
return path5.join(base, `vault-${slug}`);
|
|
3114
|
+
}
|
|
3115
|
+
async function findExistingVault(workspacePath, slug) {
|
|
3116
|
+
const slugged = resolveVaultPath(workspacePath, slug);
|
|
3117
|
+
try {
|
|
3118
|
+
await fs5.access(path5.join(slugged, ".ana-vault.json"));
|
|
3119
|
+
return slugged;
|
|
3120
|
+
} catch {
|
|
3121
|
+
}
|
|
3122
|
+
const legacy = path5.join(workspacePath, "vault");
|
|
3123
|
+
try {
|
|
3124
|
+
await fs5.access(path5.join(legacy, ".ana-vault.json"));
|
|
3125
|
+
return legacy;
|
|
3126
|
+
} catch {
|
|
3127
|
+
return null;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
async function resolveEffectiveWorkspace(courseTitle, storedPath, cwd = process.cwd(), defaultBasePath = DEFAULT_BASE) {
|
|
3131
|
+
const cwdWorkspace = await resolveCwdWorkspacePath(cwd);
|
|
3132
|
+
if (cwdWorkspace) {
|
|
3133
|
+
return { found: true, workspacePath: cwdWorkspace, source: "cwd" };
|
|
3134
|
+
}
|
|
3135
|
+
if (storedPath) {
|
|
3136
|
+
try {
|
|
3137
|
+
await fs5.access(path5.join(storedPath, ".ana-config.json"));
|
|
3138
|
+
return { found: true, workspacePath: storedPath, source: "stored" };
|
|
3139
|
+
} catch {
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
const result = await resolveWorkspace(courseTitle, defaultBasePath);
|
|
3143
|
+
if (result.found) {
|
|
3144
|
+
return { ...result, source: "default" };
|
|
3145
|
+
}
|
|
3146
|
+
return { found: false, workspacePath: null };
|
|
3147
|
+
}
|
|
3148
|
+
var DEFAULT_BASE;
|
|
3149
|
+
var init_resolve = __esm({
|
|
3150
|
+
"src/workspace/resolve.ts"() {
|
|
3151
|
+
"use strict";
|
|
3152
|
+
DEFAULT_BASE = path5.join(os6.homedir(), "study");
|
|
3153
|
+
}
|
|
3154
|
+
});
|
|
3155
|
+
|
|
3156
|
+
// src/onboarding/status.ts
|
|
3157
|
+
async function getCourseOnboardingStatus(activeCourse, configDir, cwd = process.cwd()) {
|
|
3158
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId, configDir);
|
|
3159
|
+
const initReady = Boolean(onboardingState?.initCompletedAt);
|
|
3160
|
+
const ws = await resolveEffectiveWorkspace(
|
|
3161
|
+
activeCourse.courseTitle,
|
|
3162
|
+
onboardingState?.workspacePath,
|
|
3163
|
+
cwd
|
|
3164
|
+
);
|
|
3165
|
+
return {
|
|
3166
|
+
initReady,
|
|
3167
|
+
workspaceReady: ws.found,
|
|
3168
|
+
workspacePath: ws.workspacePath,
|
|
3169
|
+
workspaceSource: ws.source
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
var init_status = __esm({
|
|
3173
|
+
"src/onboarding/status.ts"() {
|
|
3174
|
+
"use strict";
|
|
3175
|
+
init_session();
|
|
3176
|
+
init_resolve();
|
|
3177
|
+
}
|
|
3178
|
+
});
|
|
3179
|
+
|
|
3024
3180
|
// src/commands/select.ts
|
|
3025
3181
|
import { Command as Command6 } from "commander";
|
|
3182
|
+
import os7 from "node:os";
|
|
3026
3183
|
var logger5, selectCommand;
|
|
3027
3184
|
var init_select = __esm({
|
|
3028
3185
|
"src/commands/select.ts"() {
|
|
@@ -3033,6 +3190,7 @@ var init_select = __esm({
|
|
|
3033
3190
|
init_session();
|
|
3034
3191
|
init_formatter();
|
|
3035
3192
|
init_instruction_files();
|
|
3193
|
+
init_status();
|
|
3036
3194
|
logger5 = createLogger("cli:select");
|
|
3037
3195
|
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) => {
|
|
3038
3196
|
try {
|
|
@@ -3065,6 +3223,12 @@ var init_select = __esm({
|
|
|
3065
3223
|
courseTags: matched?.tags,
|
|
3066
3224
|
courseLevel: matched?.level
|
|
3067
3225
|
});
|
|
3226
|
+
const activeCourseForStatus = {
|
|
3227
|
+
courseId: detail.courseId,
|
|
3228
|
+
courseTitle: detail.courseTitle,
|
|
3229
|
+
enrollmentId
|
|
3230
|
+
};
|
|
3231
|
+
const onboarding = await getCourseOnboardingStatus(activeCourseForStatus);
|
|
3068
3232
|
let courseSlug2 = "";
|
|
3069
3233
|
try {
|
|
3070
3234
|
courseSlug2 = generateInstructionFiles({
|
|
@@ -3073,7 +3237,9 @@ var init_select = __esm({
|
|
|
3073
3237
|
progress: detail.progress,
|
|
3074
3238
|
moduleCount: detail.moduleCount,
|
|
3075
3239
|
lessonCount: detail.lessonCount,
|
|
3076
|
-
courseDescription: detail.courseDescription
|
|
3240
|
+
courseDescription: detail.courseDescription,
|
|
3241
|
+
workspaceReady: onboarding.workspaceReady,
|
|
3242
|
+
workspacePath: onboarding.workspacePath ?? void 0
|
|
3077
3243
|
});
|
|
3078
3244
|
} catch (err) {
|
|
3079
3245
|
logger5.warn("Failed to generate instruction files", {
|
|
@@ -3081,13 +3247,36 @@ var init_select = __esm({
|
|
|
3081
3247
|
});
|
|
3082
3248
|
}
|
|
3083
3249
|
if (opts.json) {
|
|
3084
|
-
output(
|
|
3250
|
+
output(
|
|
3251
|
+
{
|
|
3252
|
+
...detail,
|
|
3253
|
+
enrollmentId,
|
|
3254
|
+
courseSlug: courseSlug2,
|
|
3255
|
+
workspacePath: onboarding.workspacePath,
|
|
3256
|
+
workspaceSource: onboarding.workspaceSource
|
|
3257
|
+
},
|
|
3258
|
+
{ json: true }
|
|
3259
|
+
);
|
|
3085
3260
|
} else {
|
|
3086
3261
|
const slashCmd = courseSlug2 ? `/tostudy-${courseSlug2}` : "/tostudy";
|
|
3262
|
+
const home = os7.homedir();
|
|
3263
|
+
const cwd = process.cwd();
|
|
3264
|
+
const namespacedPath = `${cwd}/.tostudy`;
|
|
3265
|
+
let wsLine;
|
|
3266
|
+
if (onboarding.workspacePath === namespacedPath) {
|
|
3267
|
+
wsLine = ` Workspace: ${cwd.replace(home, "~")}/.tostudy/ (isolado do projeto)`;
|
|
3268
|
+
} else if (onboarding.workspacePath === cwd) {
|
|
3269
|
+
wsLine = ` Workspace: esta pasta (${cwd.replace(home, "~")})`;
|
|
3270
|
+
} else if (onboarding.workspacePath) {
|
|
3271
|
+
wsLine = ` Workspace: ${onboarding.workspacePath.replace(home, "~")}`;
|
|
3272
|
+
} else {
|
|
3273
|
+
wsLine = " Workspace: rode `tostudy workspace setup` para configurar";
|
|
3274
|
+
}
|
|
3087
3275
|
output(
|
|
3088
3276
|
[
|
|
3089
3277
|
`\u2713 Curso ativado: ${detail.courseTitle}`,
|
|
3090
3278
|
` Progresso: ${detail.progress}% | ${detail.moduleCount} m\xF3dulos | ${detail.lessonCount} li\xE7\xF5es`,
|
|
3279
|
+
wsLine,
|
|
3091
3280
|
"",
|
|
3092
3281
|
" Arquivos de contexto criados para seu assistente AI.",
|
|
3093
3282
|
"",
|
|
@@ -4686,7 +4875,7 @@ var init_query_promise = __esm({
|
|
|
4686
4875
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
4687
4876
|
const nullifyMap = {};
|
|
4688
4877
|
const result = columns.reduce(
|
|
4689
|
-
(result2, { path:
|
|
4878
|
+
(result2, { path: path14, field }, columnIndex) => {
|
|
4690
4879
|
let decoder;
|
|
4691
4880
|
if (is(field, Column)) {
|
|
4692
4881
|
decoder = field;
|
|
@@ -4698,8 +4887,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4698
4887
|
decoder = field.sql.decoder;
|
|
4699
4888
|
}
|
|
4700
4889
|
let node = result2;
|
|
4701
|
-
for (const [pathChunkIndex, pathChunk] of
|
|
4702
|
-
if (pathChunkIndex <
|
|
4890
|
+
for (const [pathChunkIndex, pathChunk] of path14.entries()) {
|
|
4891
|
+
if (pathChunkIndex < path14.length - 1) {
|
|
4703
4892
|
if (!(pathChunk in node)) {
|
|
4704
4893
|
node[pathChunk] = {};
|
|
4705
4894
|
}
|
|
@@ -4707,8 +4896,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4707
4896
|
} else {
|
|
4708
4897
|
const rawValue = row[columnIndex];
|
|
4709
4898
|
const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
|
|
4710
|
-
if (joinsNotNullableMap && is(field, Column) &&
|
|
4711
|
-
const objectName =
|
|
4899
|
+
if (joinsNotNullableMap && is(field, Column) && path14.length === 2) {
|
|
4900
|
+
const objectName = path14[0];
|
|
4712
4901
|
if (!(objectName in nullifyMap)) {
|
|
4713
4902
|
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
|
|
4714
4903
|
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
|
|
@@ -8655,13 +8844,13 @@ function Subscribe(postgres2, options) {
|
|
|
8655
8844
|
}
|
|
8656
8845
|
}
|
|
8657
8846
|
function handle(a, b2) {
|
|
8658
|
-
const
|
|
8847
|
+
const path14 = b2.relation.schema + "." + b2.relation.table;
|
|
8659
8848
|
call("*", a, b2);
|
|
8660
|
-
call("*:" +
|
|
8661
|
-
b2.relation.keys.length && call("*:" +
|
|
8849
|
+
call("*:" + path14, a, b2);
|
|
8850
|
+
b2.relation.keys.length && call("*:" + path14 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
|
|
8662
8851
|
call(b2.command, a, b2);
|
|
8663
|
-
call(b2.command + ":" +
|
|
8664
|
-
b2.relation.keys.length && call(b2.command + ":" +
|
|
8852
|
+
call(b2.command + ":" + path14, a, b2);
|
|
8853
|
+
b2.relation.keys.length && call(b2.command + ":" + path14 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
|
|
8665
8854
|
}
|
|
8666
8855
|
function pong() {
|
|
8667
8856
|
const x2 = Buffer.alloc(34);
|
|
@@ -8774,8 +8963,8 @@ function parseEvent(x) {
|
|
|
8774
8963
|
const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || [];
|
|
8775
8964
|
if (!xs)
|
|
8776
8965
|
throw new Error("Malformed subscribe pattern: " + x);
|
|
8777
|
-
const [, command,
|
|
8778
|
-
return (command || "*") + (
|
|
8966
|
+
const [, command, path14, key] = xs;
|
|
8967
|
+
return (command || "*") + (path14 ? ":" + (path14.indexOf(".") === -1 ? "public." + path14 : path14) : "") + (key ? "=" + key : "");
|
|
8779
8968
|
}
|
|
8780
8969
|
var noop2;
|
|
8781
8970
|
var init_subscribe = __esm({
|
|
@@ -8856,8 +9045,8 @@ var init_large = __esm({
|
|
|
8856
9045
|
});
|
|
8857
9046
|
|
|
8858
9047
|
// ../../node_modules/postgres/src/index.js
|
|
8859
|
-
import
|
|
8860
|
-
import
|
|
9048
|
+
import os8 from "os";
|
|
9049
|
+
import fs6 from "fs";
|
|
8861
9050
|
function Postgres(a, b2) {
|
|
8862
9051
|
const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
|
|
8863
9052
|
let ending = false;
|
|
@@ -8913,10 +9102,10 @@ function Postgres(a, b2) {
|
|
|
8913
9102
|
});
|
|
8914
9103
|
return query;
|
|
8915
9104
|
}
|
|
8916
|
-
function file2(
|
|
9105
|
+
function file2(path14, args = [], options2 = {}) {
|
|
8917
9106
|
arguments.length === 2 && !Array.isArray(args) && (options2 = args, args = []);
|
|
8918
9107
|
const query = new Query([], args, (query2) => {
|
|
8919
|
-
|
|
9108
|
+
fs6.readFile(path14, "utf8", (err, string4) => {
|
|
8920
9109
|
if (err)
|
|
8921
9110
|
return query2.reject(err);
|
|
8922
9111
|
query2.strings = [string4];
|
|
@@ -9234,7 +9423,7 @@ function parseUrl(url2) {
|
|
|
9234
9423
|
}
|
|
9235
9424
|
function osUsername() {
|
|
9236
9425
|
try {
|
|
9237
|
-
return
|
|
9426
|
+
return os8.userInfo().username;
|
|
9238
9427
|
} catch (_) {
|
|
9239
9428
|
return process.env.USERNAME || process.env.USER || process.env.LOGNAME;
|
|
9240
9429
|
}
|
|
@@ -13495,12 +13684,12 @@ var init_session3 = __esm({
|
|
|
13495
13684
|
init_tracing();
|
|
13496
13685
|
init_utils();
|
|
13497
13686
|
PostgresJsPreparedQuery = class extends PgPreparedQuery {
|
|
13498
|
-
constructor(client, queryString, params,
|
|
13687
|
+
constructor(client, queryString, params, logger19, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
|
|
13499
13688
|
super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
|
|
13500
13689
|
this.client = client;
|
|
13501
13690
|
this.queryString = queryString;
|
|
13502
13691
|
this.params = params;
|
|
13503
|
-
this.logger =
|
|
13692
|
+
this.logger = logger19;
|
|
13504
13693
|
this.fields = fields;
|
|
13505
13694
|
this._isResponseInArrayMode = _isResponseInArrayMode;
|
|
13506
13695
|
this.customResultMapper = customResultMapper;
|
|
@@ -13641,11 +13830,11 @@ function construct(client, config2 = {}) {
|
|
|
13641
13830
|
client.options.serializers["114"] = transparentParser;
|
|
13642
13831
|
client.options.serializers["3802"] = transparentParser;
|
|
13643
13832
|
const dialect = new PgDialect({ casing: config2.casing });
|
|
13644
|
-
let
|
|
13833
|
+
let logger19;
|
|
13645
13834
|
if (config2.logger === true) {
|
|
13646
|
-
|
|
13835
|
+
logger19 = new DefaultLogger();
|
|
13647
13836
|
} else if (config2.logger !== false) {
|
|
13648
|
-
|
|
13837
|
+
logger19 = config2.logger;
|
|
13649
13838
|
}
|
|
13650
13839
|
let schema;
|
|
13651
13840
|
if (config2.schema) {
|
|
@@ -13659,7 +13848,7 @@ function construct(client, config2 = {}) {
|
|
|
13659
13848
|
tableNamesMap: tablesConfig.tableNamesMap
|
|
13660
13849
|
};
|
|
13661
13850
|
}
|
|
13662
|
-
const session = new PostgresJsSession(client, dialect, schema, { logger:
|
|
13851
|
+
const session = new PostgresJsSession(client, dialect, schema, { logger: logger19, cache: config2.cache });
|
|
13663
13852
|
const db2 = new PostgresJsDatabase(dialect, session, schema);
|
|
13664
13853
|
db2.$client = client;
|
|
13665
13854
|
db2.$cache = config2.cache;
|
|
@@ -13861,7 +14050,7 @@ __export(util_exports, {
|
|
|
13861
14050
|
required: () => required,
|
|
13862
14051
|
safeExtend: () => safeExtend,
|
|
13863
14052
|
shallowClone: () => shallowClone,
|
|
13864
|
-
slugify: () =>
|
|
14053
|
+
slugify: () => slugify2,
|
|
13865
14054
|
stringifyPrimitive: () => stringifyPrimitive,
|
|
13866
14055
|
uint8ArrayToBase64: () => uint8ArrayToBase64,
|
|
13867
14056
|
uint8ArrayToBase64url: () => uint8ArrayToBase64url,
|
|
@@ -13974,10 +14163,10 @@ function mergeDefs(...defs) {
|
|
|
13974
14163
|
function cloneDef(schema) {
|
|
13975
14164
|
return mergeDefs(schema._zod.def);
|
|
13976
14165
|
}
|
|
13977
|
-
function getElementAtPath(obj,
|
|
13978
|
-
if (!
|
|
14166
|
+
function getElementAtPath(obj, path14) {
|
|
14167
|
+
if (!path14)
|
|
13979
14168
|
return obj;
|
|
13980
|
-
return
|
|
14169
|
+
return path14.reduce((acc, key) => acc?.[key], obj);
|
|
13981
14170
|
}
|
|
13982
14171
|
function promiseAllObject(promisesObj) {
|
|
13983
14172
|
const keys = Object.keys(promisesObj);
|
|
@@ -14001,7 +14190,7 @@ function randomString(length = 10) {
|
|
|
14001
14190
|
function esc(str) {
|
|
14002
14191
|
return JSON.stringify(str);
|
|
14003
14192
|
}
|
|
14004
|
-
function
|
|
14193
|
+
function slugify2(input2) {
|
|
14005
14194
|
return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
14006
14195
|
}
|
|
14007
14196
|
function isObject(data) {
|
|
@@ -14289,11 +14478,11 @@ function aborted(x, startIndex = 0) {
|
|
|
14289
14478
|
}
|
|
14290
14479
|
return false;
|
|
14291
14480
|
}
|
|
14292
|
-
function prefixIssues(
|
|
14481
|
+
function prefixIssues(path14, issues) {
|
|
14293
14482
|
return issues.map((iss) => {
|
|
14294
14483
|
var _a2;
|
|
14295
14484
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
14296
|
-
iss.path.unshift(
|
|
14485
|
+
iss.path.unshift(path14);
|
|
14297
14486
|
return iss;
|
|
14298
14487
|
});
|
|
14299
14488
|
}
|
|
@@ -14535,7 +14724,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14535
14724
|
}
|
|
14536
14725
|
function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
14537
14726
|
const result = { errors: [] };
|
|
14538
|
-
const processError = (error50,
|
|
14727
|
+
const processError = (error50, path14 = []) => {
|
|
14539
14728
|
var _a2, _b;
|
|
14540
14729
|
for (const issue2 of error50.issues) {
|
|
14541
14730
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -14545,7 +14734,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14545
14734
|
} else if (issue2.code === "invalid_element") {
|
|
14546
14735
|
processError({ issues: issue2.issues }, issue2.path);
|
|
14547
14736
|
} else {
|
|
14548
|
-
const fullpath = [...
|
|
14737
|
+
const fullpath = [...path14, ...issue2.path];
|
|
14549
14738
|
if (fullpath.length === 0) {
|
|
14550
14739
|
result.errors.push(mapper(issue2));
|
|
14551
14740
|
continue;
|
|
@@ -14577,8 +14766,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
|
|
|
14577
14766
|
}
|
|
14578
14767
|
function toDotPath(_path) {
|
|
14579
14768
|
const segs = [];
|
|
14580
|
-
const
|
|
14581
|
-
for (const seg of
|
|
14769
|
+
const path14 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
14770
|
+
for (const seg of path14) {
|
|
14582
14771
|
if (typeof seg === "number")
|
|
14583
14772
|
segs.push(`[${seg}]`);
|
|
14584
14773
|
else if (typeof seg === "symbol")
|
|
@@ -24135,7 +24324,7 @@ function _toUpperCase() {
|
|
|
24135
24324
|
}
|
|
24136
24325
|
// @__NO_SIDE_EFFECTS__
|
|
24137
24326
|
function _slugify() {
|
|
24138
|
-
return /* @__PURE__ */ _overwrite((input2) =>
|
|
24327
|
+
return /* @__PURE__ */ _overwrite((input2) => slugify2(input2));
|
|
24139
24328
|
}
|
|
24140
24329
|
// @__NO_SIDE_EFFECTS__
|
|
24141
24330
|
function _array(Class2, element, params) {
|
|
@@ -27272,13 +27461,13 @@ function resolveRef(ref, ctx) {
|
|
|
27272
27461
|
if (!ref.startsWith("#")) {
|
|
27273
27462
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
27274
27463
|
}
|
|
27275
|
-
const
|
|
27276
|
-
if (
|
|
27464
|
+
const path14 = ref.slice(1).split("/").filter(Boolean);
|
|
27465
|
+
if (path14.length === 0) {
|
|
27277
27466
|
return ctx.rootSchema;
|
|
27278
27467
|
}
|
|
27279
27468
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
27280
|
-
if (
|
|
27281
|
-
const key =
|
|
27469
|
+
if (path14[0] === defsKey) {
|
|
27470
|
+
const key = path14[1];
|
|
27282
27471
|
if (!key || !ctx.defs[key]) {
|
|
27283
27472
|
throw new Error(`Reference not found: ${ref}`);
|
|
27284
27473
|
}
|
|
@@ -28467,7 +28656,11 @@ var init_courses3 = __esm({
|
|
|
28467
28656
|
// Learning Path Graph: Reference to migrated graph (null if not migrated)
|
|
28468
28657
|
migratedToGraphId: uuid("migrated_to_graph_id"),
|
|
28469
28658
|
// Study Channel Configuration: which channels are available for this course
|
|
28470
|
-
channelConfig: jsonb("channel_config").$type()
|
|
28659
|
+
channelConfig: jsonb("channel_config").$type(),
|
|
28660
|
+
// Course Quality Dashboard: Cached health score (0-100 with tier + dimensions)
|
|
28661
|
+
healthScore: jsonb("health_score").$type(),
|
|
28662
|
+
// Certificate: AI-generated course summary for PDF certificates (max 200 chars)
|
|
28663
|
+
certificateSummary: varchar("certificate_summary", { length: 200 })
|
|
28471
28664
|
},
|
|
28472
28665
|
(table) => ({
|
|
28473
28666
|
creatorIdIdx: index("courses_creator_id_idx").on(table.creatorId),
|
|
@@ -32903,29 +33096,32 @@ var init_validation_attempts = __esm({
|
|
|
32903
33096
|
init_users();
|
|
32904
33097
|
init_enrollments();
|
|
32905
33098
|
init_courses3();
|
|
32906
|
-
validationAttempts2 = pgTable(
|
|
32907
|
-
|
|
32908
|
-
|
|
32909
|
-
|
|
32910
|
-
|
|
32911
|
-
|
|
32912
|
-
|
|
32913
|
-
|
|
32914
|
-
|
|
32915
|
-
|
|
32916
|
-
|
|
32917
|
-
|
|
32918
|
-
|
|
32919
|
-
|
|
32920
|
-
|
|
32921
|
-
|
|
32922
|
-
|
|
32923
|
-
|
|
32924
|
-
|
|
32925
|
-
|
|
32926
|
-
|
|
32927
|
-
|
|
32928
|
-
|
|
33099
|
+
validationAttempts2 = pgTable(
|
|
33100
|
+
"validation_attempts",
|
|
33101
|
+
{
|
|
33102
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
33103
|
+
lessonId: uuid("lesson_id").references(() => lessons.id, { onDelete: "cascade" }).notNull(),
|
|
33104
|
+
userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
|
|
33105
|
+
passed: boolean("passed").notNull(),
|
|
33106
|
+
score: real("score"),
|
|
33107
|
+
feedback: text("feedback"),
|
|
33108
|
+
missingCriteria: jsonb("missing_criteria").$type(),
|
|
33109
|
+
attemptedAt: timestamp("attempted_at").defaultNow().notNull(),
|
|
33110
|
+
// v2 — validation persistence & learning analytics
|
|
33111
|
+
enrollmentId: uuid("enrollment_id").references(() => enrollments.id, { onDelete: "cascade" }),
|
|
33112
|
+
courseId: uuid("course_id").references(() => courses.id, { onDelete: "cascade" }),
|
|
33113
|
+
exerciseIndex: integer("exercise_index"),
|
|
33114
|
+
solution: text("solution"),
|
|
33115
|
+
criteriaResults: jsonb("criteria_results").$type(),
|
|
33116
|
+
source: text("source").$type(),
|
|
33117
|
+
duration: integer("duration")
|
|
33118
|
+
},
|
|
33119
|
+
(table) => [
|
|
33120
|
+
index("va_user_lesson_exercise_idx").on(table.userId, table.lessonId, table.exerciseIndex),
|
|
33121
|
+
index("va_course_lesson_passed_idx").on(table.courseId, table.lessonId, table.passed),
|
|
33122
|
+
index("va_user_course_attempted_idx").on(table.userId, table.courseId, table.attemptedAt)
|
|
33123
|
+
]
|
|
33124
|
+
);
|
|
32929
33125
|
}
|
|
32930
33126
|
});
|
|
32931
33127
|
|
|
@@ -35108,77 +35304,6 @@ var init_course_variants = __esm({
|
|
|
35108
35304
|
}
|
|
35109
35305
|
});
|
|
35110
35306
|
|
|
35111
|
-
// ../../packages/database/src/schema/_archived/user-credits.ts
|
|
35112
|
-
var transactionTypeEnum, userCredits2, creditTransactions2;
|
|
35113
|
-
var init_user_credits = __esm({
|
|
35114
|
-
"../../packages/database/src/schema/_archived/user-credits.ts"() {
|
|
35115
|
-
"use strict";
|
|
35116
|
-
init_pg_core();
|
|
35117
|
-
init_users();
|
|
35118
|
-
transactionTypeEnum = pgEnum("transaction_type", [
|
|
35119
|
-
"initial_grant",
|
|
35120
|
-
// Credito inicial de $5
|
|
35121
|
-
"purchase",
|
|
35122
|
-
// Compra de creditos
|
|
35123
|
-
"consumption",
|
|
35124
|
-
// Consumo por uso de IA
|
|
35125
|
-
"refund",
|
|
35126
|
-
// Reembolso
|
|
35127
|
-
"adjustment"
|
|
35128
|
-
// Ajuste manual admin
|
|
35129
|
-
]);
|
|
35130
|
-
userCredits2 = pgTable(
|
|
35131
|
-
"user_credits",
|
|
35132
|
-
{
|
|
35133
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
35134
|
-
userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }).notNull().unique(),
|
|
35135
|
-
/** Current balance in USD */
|
|
35136
|
-
balance: decimal("balance", { precision: 10, scale: 4 }).notNull().default("0"),
|
|
35137
|
-
/** Total consumed in USD (absolute value, always positive) */
|
|
35138
|
-
totalConsumed: decimal("total_consumed", { precision: 10, scale: 4 }).notNull().default("0"),
|
|
35139
|
-
/** Total purchased in USD */
|
|
35140
|
-
totalPurchased: decimal("total_purchased", { precision: 10, scale: 4 }).notNull().default("0"),
|
|
35141
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
35142
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
35143
|
-
},
|
|
35144
|
-
(table) => ({
|
|
35145
|
-
userIdIdx: index("user_credits_user_id_idx").on(table.userId)
|
|
35146
|
-
})
|
|
35147
|
-
);
|
|
35148
|
-
creditTransactions2 = pgTable(
|
|
35149
|
-
"credit_transactions",
|
|
35150
|
-
{
|
|
35151
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
35152
|
-
userId: uuid("user_id").references(() => users.id, { onDelete: "restrict" }).notNull(),
|
|
35153
|
-
type: transactionTypeEnum("type").notNull(),
|
|
35154
|
-
/** Amount in USD (negative for consumption, positive for purchase/grant) */
|
|
35155
|
-
amount: decimal("amount", { precision: 10, scale: 4 }).notNull(),
|
|
35156
|
-
/** Balance after this transaction in USD */
|
|
35157
|
-
balanceAfter: decimal("balance_after", { precision: 10, scale: 4 }).notNull(),
|
|
35158
|
-
description: text("description"),
|
|
35159
|
-
metadata: text("metadata"),
|
|
35160
|
-
// JSON com detalhes (tokens, modelo, etc)
|
|
35161
|
-
// Granular usage tracking columns
|
|
35162
|
-
operation: varchar("operation", { length: 50 }),
|
|
35163
|
-
// 'brainstorm_message', 'generation_outline', etc.
|
|
35164
|
-
model: varchar("model", { length: 100 }),
|
|
35165
|
-
// 'claude-sonnet-4-5-20250929'
|
|
35166
|
-
inputTokens: integer("input_tokens"),
|
|
35167
|
-
outputTokens: integer("output_tokens"),
|
|
35168
|
-
latencyMs: integer("latency_ms"),
|
|
35169
|
-
// DB FK to course_proposals preserved in migration; Drizzle ref removed for schema archival
|
|
35170
|
-
proposalId: uuid("proposal_id"),
|
|
35171
|
-
sessionId: uuid("session_id"),
|
|
35172
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
35173
|
-
},
|
|
35174
|
-
(table) => ({
|
|
35175
|
-
userIdIdx: index("credit_transactions_user_id_idx").on(table.userId),
|
|
35176
|
-
createdAtIdx: index("credit_transactions_created_at_idx").on(table.createdAt)
|
|
35177
|
-
})
|
|
35178
|
-
);
|
|
35179
|
-
}
|
|
35180
|
-
});
|
|
35181
|
-
|
|
35182
35307
|
// ../../packages/database/src/schema/_archived/index.ts
|
|
35183
35308
|
var init_archived = __esm({
|
|
35184
35309
|
"../../packages/database/src/schema/_archived/index.ts"() {
|
|
@@ -35187,7 +35312,6 @@ var init_archived = __esm({
|
|
|
35187
35312
|
init_course_matrices();
|
|
35188
35313
|
init_matrix_modules();
|
|
35189
35314
|
init_course_variants();
|
|
35190
|
-
init_user_credits();
|
|
35191
35315
|
}
|
|
35192
35316
|
});
|
|
35193
35317
|
|
|
@@ -36394,7 +36518,6 @@ var init_system_config = __esm({
|
|
|
36394
36518
|
"business.limits.free_brainstorm_sessions": "business.limits.free_brainstorm_sessions",
|
|
36395
36519
|
// Feature Flags
|
|
36396
36520
|
"feature.spark_chat_enabled": "feature.spark_chat_enabled",
|
|
36397
|
-
"feature.ai_course_generation_enabled": "feature.ai_course_generation_enabled",
|
|
36398
36521
|
"feature.analytics_enabled": "feature.analytics_enabled",
|
|
36399
36522
|
"feature.mentorship_enabled": "feature.mentorship_enabled",
|
|
36400
36523
|
"feature.community_enabled": "feature.community_enabled",
|
|
@@ -38873,7 +38996,6 @@ __export(schema_exports, {
|
|
|
38873
38996
|
creditSubscriptionPlans: () => creditSubscriptionPlans,
|
|
38874
38997
|
creditSubscriptionStatusEnum: () => creditSubscriptionStatusEnum,
|
|
38875
38998
|
creditSubscriptions: () => creditSubscriptions,
|
|
38876
|
-
creditTransactions: () => creditTransactions2,
|
|
38877
38999
|
creditWalletTransactionTypeEnum: () => creditWalletTransactionTypeEnum,
|
|
38878
39000
|
creditWalletTransactions: () => creditWalletTransactions,
|
|
38879
39001
|
creditWallets: () => creditWallets,
|
|
@@ -39159,7 +39281,6 @@ __export(schema_exports, {
|
|
|
39159
39281
|
testimonialStatusEnum: () => testimonialStatusEnum,
|
|
39160
39282
|
threadStatusEnum: () => threadStatusEnum,
|
|
39161
39283
|
toneEnum: () => toneEnum,
|
|
39162
|
-
transactionTypeEnum: () => transactionTypeEnum,
|
|
39163
39284
|
transactions: () => transactions2,
|
|
39164
39285
|
transactionsRelations: () => transactionsRelations2,
|
|
39165
39286
|
transferStatusEnum: () => transferStatusEnum,
|
|
@@ -39176,7 +39297,6 @@ __export(schema_exports, {
|
|
|
39176
39297
|
userBadgesRelations: () => userBadgesRelations2,
|
|
39177
39298
|
userChallengeScores: () => userChallengeScores,
|
|
39178
39299
|
userChallengeScoresRelations: () => userChallengeScoresRelations,
|
|
39179
|
-
userCredits: () => userCredits2,
|
|
39180
39300
|
userFollows: () => userFollows2,
|
|
39181
39301
|
userFollowsRelations: () => userFollowsRelations2,
|
|
39182
39302
|
userPathEnrollments: () => userPathEnrollments,
|
|
@@ -40873,86 +40993,16 @@ var init_lessons2 = __esm({
|
|
|
40873
40993
|
}
|
|
40874
40994
|
});
|
|
40875
40995
|
|
|
40876
|
-
// src/workspace/resolve.ts
|
|
40877
|
-
import fs6 from "node:fs/promises";
|
|
40878
|
-
import path5 from "node:path";
|
|
40879
|
-
import os6 from "node:os";
|
|
40880
|
-
function courseSlug(title) {
|
|
40881
|
-
return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
40882
|
-
}
|
|
40883
|
-
async function resolveWorkspace(courseTitle, basePath = DEFAULT_BASE) {
|
|
40884
|
-
const slug = courseSlug(courseTitle);
|
|
40885
|
-
const candidate = path5.join(basePath, slug);
|
|
40886
|
-
try {
|
|
40887
|
-
await fs6.access(path5.join(candidate, ".ana-config.json"));
|
|
40888
|
-
return { found: true, workspacePath: candidate };
|
|
40889
|
-
} catch {
|
|
40890
|
-
return { found: false, workspacePath: null };
|
|
40891
|
-
}
|
|
40892
|
-
}
|
|
40893
|
-
var DEFAULT_BASE;
|
|
40894
|
-
var init_resolve = __esm({
|
|
40895
|
-
"src/workspace/resolve.ts"() {
|
|
40896
|
-
"use strict";
|
|
40897
|
-
DEFAULT_BASE = path5.join(os6.homedir(), "study");
|
|
40898
|
-
}
|
|
40899
|
-
});
|
|
40900
|
-
|
|
40901
|
-
// src/onboarding/status.ts
|
|
40902
|
-
import fs7 from "node:fs/promises";
|
|
40903
|
-
import path6 from "node:path";
|
|
40904
|
-
async function resolveStoredWorkspace(workspacePath) {
|
|
40905
|
-
if (!workspacePath) return null;
|
|
40906
|
-
try {
|
|
40907
|
-
await fs7.access(path6.join(workspacePath, ".ana-config.json"));
|
|
40908
|
-
return workspacePath;
|
|
40909
|
-
} catch {
|
|
40910
|
-
return null;
|
|
40911
|
-
}
|
|
40912
|
-
}
|
|
40913
|
-
async function getCourseOnboardingStatus(activeCourse, configDir) {
|
|
40914
|
-
const onboardingState = await getCourseOnboardingState(activeCourse.courseId, configDir);
|
|
40915
|
-
const initReady = Boolean(onboardingState?.initCompletedAt);
|
|
40916
|
-
let workspacePath = await resolveStoredWorkspace(onboardingState?.workspacePath);
|
|
40917
|
-
if (!workspacePath) {
|
|
40918
|
-
const resolvedWorkspace = await resolveWorkspace(activeCourse.courseTitle);
|
|
40919
|
-
workspacePath = resolvedWorkspace.workspacePath;
|
|
40920
|
-
}
|
|
40921
|
-
return {
|
|
40922
|
-
initReady,
|
|
40923
|
-
workspaceReady: Boolean(workspacePath),
|
|
40924
|
-
workspacePath
|
|
40925
|
-
};
|
|
40926
|
-
}
|
|
40927
|
-
var init_status = __esm({
|
|
40928
|
-
"src/onboarding/status.ts"() {
|
|
40929
|
-
"use strict";
|
|
40930
|
-
init_session();
|
|
40931
|
-
init_resolve();
|
|
40932
|
-
}
|
|
40933
|
-
});
|
|
40934
|
-
|
|
40935
40996
|
// src/commands/start.ts
|
|
40936
40997
|
import { Command as Command8 } from "commander";
|
|
40937
|
-
function buildOnboardingBlockerMessage(courseTitle, onboarding) {
|
|
40938
|
-
const lines = [`Onboarding obrigat\xF3rio pendente para "${courseTitle}".`];
|
|
40939
|
-
if (!onboarding.initReady) {
|
|
40940
|
-
lines.push("Execute `tostudy init` primeiro para configurar o tutor deste curso.");
|
|
40941
|
-
}
|
|
40942
|
-
if (!onboarding.workspaceReady) {
|
|
40943
|
-
lines.push("Execute `tostudy workspace setup` para criar o workspace local antes de iniciar.");
|
|
40944
|
-
}
|
|
40945
|
-
return lines.join("\n");
|
|
40946
|
-
}
|
|
40947
40998
|
async function runStart(opts, deps = defaultDeps2) {
|
|
40948
40999
|
try {
|
|
40949
41000
|
const session = await deps.requireSession();
|
|
40950
41001
|
const activeCourse = await deps.requireActiveCourse();
|
|
40951
41002
|
const onboarding = await deps.getCourseOnboardingStatus(activeCourse);
|
|
40952
|
-
if (!onboarding.initReady
|
|
40953
|
-
|
|
40954
|
-
|
|
40955
|
-
);
|
|
41003
|
+
if (!onboarding.initReady) {
|
|
41004
|
+
deps.stderrWrite(`Dica: rode \`tostudy init\` para personalizar o tutor ao seu contexto.
|
|
41005
|
+
`);
|
|
40956
41006
|
}
|
|
40957
41007
|
const driftWarning = await deps.checkCourseDrift();
|
|
40958
41008
|
if (driftWarning) deps.stderrWrite(driftWarning + "\n");
|
|
@@ -40977,7 +41027,7 @@ async function runStart(opts, deps = defaultDeps2) {
|
|
|
40977
41027
|
deps.error(msg);
|
|
40978
41028
|
}
|
|
40979
41029
|
}
|
|
40980
|
-
var logger7, defaultDeps2,
|
|
41030
|
+
var logger7, defaultDeps2, startCommand;
|
|
40981
41031
|
var init_start = __esm({
|
|
40982
41032
|
"src/commands/start.ts"() {
|
|
40983
41033
|
"use strict";
|
|
@@ -41002,8 +41052,6 @@ var init_start = __esm({
|
|
|
41002
41052
|
stderrWrite: (message) => process.stderr.write(message),
|
|
41003
41053
|
logger: logger7
|
|
41004
41054
|
};
|
|
41005
|
-
StartBlockedError = class extends Error {
|
|
41006
|
-
};
|
|
41007
41055
|
startCommand = new Command8("start").description("Start (or resume) the current module of the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
41008
41056
|
await runStart(opts);
|
|
41009
41057
|
});
|
|
@@ -41180,10 +41228,14 @@ var init_lesson = __esm({
|
|
|
41180
41228
|
output(formatLessonContent(content), { json: false });
|
|
41181
41229
|
}
|
|
41182
41230
|
if (content.type === "exercise") {
|
|
41183
|
-
const
|
|
41231
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
41232
|
+
const ws = await resolveEffectiveWorkspace(
|
|
41233
|
+
activeCourse.courseTitle,
|
|
41234
|
+
onboardingState?.workspacePath
|
|
41235
|
+
);
|
|
41184
41236
|
if (!ws.found) {
|
|
41185
41237
|
process.stderr.write(
|
|
41186
|
-
"\n\u{1F4A1} Dica: rode `tostudy
|
|
41238
|
+
"\n\u{1F4A1} Dica: rode `tostudy select` desta pasta para us\xE1-la como workspace, ou `tostudy workspace setup` para criar em ~/study/.\n"
|
|
41187
41239
|
);
|
|
41188
41240
|
}
|
|
41189
41241
|
}
|
|
@@ -41260,8 +41312,8 @@ var init_exercises = __esm({
|
|
|
41260
41312
|
});
|
|
41261
41313
|
|
|
41262
41314
|
// src/commands/validate.ts
|
|
41263
|
-
import
|
|
41264
|
-
import
|
|
41315
|
+
import fs7 from "node:fs";
|
|
41316
|
+
import path6 from "node:path";
|
|
41265
41317
|
import { Command as Command13 } from "commander";
|
|
41266
41318
|
var logger12, validateCommand;
|
|
41267
41319
|
var init_validate = __esm({
|
|
@@ -41286,17 +41338,17 @@ var init_validate = __esm({
|
|
|
41286
41338
|
}
|
|
41287
41339
|
let solution;
|
|
41288
41340
|
if (opts.stdin) {
|
|
41289
|
-
solution =
|
|
41341
|
+
solution = fs7.readFileSync("/dev/stdin", "utf-8");
|
|
41290
41342
|
} else if (file2) {
|
|
41291
|
-
if (!
|
|
41343
|
+
if (!fs7.existsSync(file2)) {
|
|
41292
41344
|
error(`Arquivo n\xE3o encontrado: ${file2}`);
|
|
41293
41345
|
}
|
|
41294
|
-
solution =
|
|
41346
|
+
solution = fs7.readFileSync(file2, "utf-8");
|
|
41295
41347
|
} else {
|
|
41296
41348
|
error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
|
|
41297
41349
|
}
|
|
41298
41350
|
if (file2 && activeCourse.courseTags?.length) {
|
|
41299
|
-
const ext =
|
|
41351
|
+
const ext = path6.extname(file2).toLowerCase();
|
|
41300
41352
|
const LANG_EXTENSIONS = {
|
|
41301
41353
|
".html": ["html", "html5"],
|
|
41302
41354
|
".css": ["css"],
|
|
@@ -41729,14 +41781,14 @@ var init_init = __esm({
|
|
|
41729
41781
|
});
|
|
41730
41782
|
|
|
41731
41783
|
// ../../packages/tostudy-core/src/workspace/setup-workspace.ts
|
|
41732
|
-
import
|
|
41733
|
-
import
|
|
41784
|
+
import fs8 from "node:fs/promises";
|
|
41785
|
+
import path7 from "node:path";
|
|
41734
41786
|
async function setupWorkspace(input2) {
|
|
41735
|
-
const workspacePath =
|
|
41787
|
+
const workspacePath = path7.join(input2.basePath, input2.courseSlug);
|
|
41736
41788
|
for (const dir of WORKSPACE_DIRS) {
|
|
41737
|
-
await
|
|
41789
|
+
await fs8.mkdir(path7.join(workspacePath, dir), { recursive: true });
|
|
41738
41790
|
}
|
|
41739
|
-
const configPath =
|
|
41791
|
+
const configPath = path7.join(workspacePath, ".ana-config.json");
|
|
41740
41792
|
const config2 = {
|
|
41741
41793
|
courseId: input2.courseId,
|
|
41742
41794
|
courseSlug: input2.courseSlug,
|
|
@@ -41746,7 +41798,7 @@ async function setupWorkspace(input2) {
|
|
|
41746
41798
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41747
41799
|
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
41748
41800
|
};
|
|
41749
|
-
await
|
|
41801
|
+
await fs8.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
41750
41802
|
const readme = [
|
|
41751
41803
|
`# ${input2.courseName}`,
|
|
41752
41804
|
"",
|
|
@@ -41771,7 +41823,7 @@ async function setupWorkspace(input2) {
|
|
|
41771
41823
|
"tostudy vault sync # Sincronizar progresso",
|
|
41772
41824
|
"```"
|
|
41773
41825
|
].join("\n");
|
|
41774
|
-
await
|
|
41826
|
+
await fs8.writeFile(path7.join(workspacePath, "README.md"), readme, "utf-8");
|
|
41775
41827
|
return { workspacePath, directories: WORKSPACE_DIRS, configPath };
|
|
41776
41828
|
}
|
|
41777
41829
|
var WORKSPACE_DIRS;
|
|
@@ -41872,8 +41924,8 @@ var init_templates = __esm({
|
|
|
41872
41924
|
});
|
|
41873
41925
|
|
|
41874
41926
|
// ../../packages/tostudy-core/src/workspace/extract-exercise.ts
|
|
41875
|
-
import
|
|
41876
|
-
import
|
|
41927
|
+
import fs9 from "node:fs/promises";
|
|
41928
|
+
import path8 from "node:path";
|
|
41877
41929
|
function padOrder(n) {
|
|
41878
41930
|
return String(n).padStart(2, "0");
|
|
41879
41931
|
}
|
|
@@ -41897,16 +41949,16 @@ async function extractExercise(input2) {
|
|
|
41897
41949
|
const { lessonData, exerciseTier, workspacePath } = input2;
|
|
41898
41950
|
const moduleDir = `${padOrder(lessonData.moduleOrder)}-${lessonData.moduleSlug}`;
|
|
41899
41951
|
const lessonDir = `${padOrder(lessonData.lessonOrder)}-${lessonData.lessonSlug}`;
|
|
41900
|
-
const exercisePath =
|
|
41901
|
-
await
|
|
41952
|
+
const exercisePath = path8.join(workspacePath, "exercises", moduleDir, lessonDir);
|
|
41953
|
+
await fs9.mkdir(exercisePath, { recursive: true });
|
|
41902
41954
|
const extractedFiles = [];
|
|
41903
41955
|
let hasStarterCode = false;
|
|
41904
41956
|
if (lessonData.sandpackConfig?.files) {
|
|
41905
41957
|
for (const [filePath, fileData] of Object.entries(lessonData.sandpackConfig.files)) {
|
|
41906
41958
|
const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
41907
|
-
const fullPath =
|
|
41908
|
-
await
|
|
41909
|
-
await
|
|
41959
|
+
const fullPath = path8.join(exercisePath, cleanPath);
|
|
41960
|
+
await fs9.mkdir(path8.dirname(fullPath), { recursive: true });
|
|
41961
|
+
await fs9.writeFile(fullPath, fileData.code, "utf-8");
|
|
41910
41962
|
extractedFiles.push(cleanPath);
|
|
41911
41963
|
hasStarterCode = true;
|
|
41912
41964
|
}
|
|
@@ -41914,13 +41966,13 @@ async function extractExercise(input2) {
|
|
|
41914
41966
|
const tierData = getTierData(lessonData.structuredData, exerciseTier);
|
|
41915
41967
|
const tierCode = tierData?.code;
|
|
41916
41968
|
if (tierCode) {
|
|
41917
|
-
await
|
|
41969
|
+
await fs9.writeFile(path8.join(exercisePath, "exercise.js"), tierCode, "utf-8");
|
|
41918
41970
|
extractedFiles.push("exercise.js");
|
|
41919
41971
|
hasStarterCode = true;
|
|
41920
41972
|
} else {
|
|
41921
41973
|
const starter = getStarterCode(lessonData.structuredData);
|
|
41922
41974
|
if (starter) {
|
|
41923
|
-
await
|
|
41975
|
+
await fs9.writeFile(path8.join(exercisePath, "exercise.js"), starter, "utf-8");
|
|
41924
41976
|
extractedFiles.push("exercise.js");
|
|
41925
41977
|
hasStarterCode = true;
|
|
41926
41978
|
}
|
|
@@ -41938,8 +41990,8 @@ async function extractExercise(input2) {
|
|
|
41938
41990
|
...exerciseDeps
|
|
41939
41991
|
}
|
|
41940
41992
|
};
|
|
41941
|
-
await
|
|
41942
|
-
|
|
41993
|
+
await fs9.writeFile(
|
|
41994
|
+
path8.join(exercisePath, "package.json"),
|
|
41943
41995
|
JSON.stringify(pkgJson, null, 2),
|
|
41944
41996
|
"utf-8"
|
|
41945
41997
|
);
|
|
@@ -41951,20 +42003,20 @@ async function extractExercise(input2) {
|
|
|
41951
42003
|
);
|
|
41952
42004
|
for (const [configFile, configContent] of Object.entries(scaffold.configs)) {
|
|
41953
42005
|
if (!sandpackFileNames.has(configFile)) {
|
|
41954
|
-
await
|
|
42006
|
+
await fs9.writeFile(path8.join(exercisePath, configFile), configContent, "utf-8");
|
|
41955
42007
|
extractedFiles.push(configFile);
|
|
41956
42008
|
}
|
|
41957
42009
|
}
|
|
41958
42010
|
const setupSh = `#!/bin/sh
|
|
41959
42011
|
${scaffold.setupScript}
|
|
41960
42012
|
`;
|
|
41961
|
-
await
|
|
42013
|
+
await fs9.writeFile(path8.join(exercisePath, "setup.sh"), setupSh, "utf-8");
|
|
41962
42014
|
extractedFiles.push("setup.sh");
|
|
41963
42015
|
}
|
|
41964
42016
|
}
|
|
41965
42017
|
const readme = generateReadme(lessonData, exerciseTier);
|
|
41966
|
-
const readmePath =
|
|
41967
|
-
await
|
|
42018
|
+
const readmePath = path8.join(exercisePath, "README.md");
|
|
42019
|
+
await fs9.writeFile(readmePath, readme, "utf-8");
|
|
41968
42020
|
extractedFiles.push("README.md");
|
|
41969
42021
|
return {
|
|
41970
42022
|
exercisePath,
|
|
@@ -42036,9 +42088,9 @@ var init_workspace = __esm({
|
|
|
42036
42088
|
|
|
42037
42089
|
// src/commands/workspace.ts
|
|
42038
42090
|
import { Command as Command16 } from "commander";
|
|
42039
|
-
import
|
|
42040
|
-
import
|
|
42041
|
-
import
|
|
42091
|
+
import path9 from "node:path";
|
|
42092
|
+
import os9 from "node:os";
|
|
42093
|
+
import fs10 from "node:fs/promises";
|
|
42042
42094
|
var logger14, workspaceCommand;
|
|
42043
42095
|
var init_workspace2 = __esm({
|
|
42044
42096
|
"src/commands/workspace.ts"() {
|
|
@@ -42046,31 +42098,67 @@ var init_workspace2 = __esm({
|
|
|
42046
42098
|
init_src();
|
|
42047
42099
|
init_workspace();
|
|
42048
42100
|
init_session();
|
|
42101
|
+
init_resolve();
|
|
42049
42102
|
logger14 = createLogger("cli:workspace");
|
|
42050
42103
|
workspaceCommand = new Command16("workspace").description(
|
|
42051
42104
|
"Gerenciar workspace de estudo local"
|
|
42052
42105
|
);
|
|
42053
|
-
workspaceCommand.command("setup").description("Criar estrutura do workspace para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace
|
|
42106
|
+
workspaceCommand.command("setup").description("Criar estrutura do workspace para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace (omita para usar a pasta atual)").option("--json", "Output structured JSON").action(async (opts) => {
|
|
42054
42107
|
try {
|
|
42055
42108
|
await requireSession();
|
|
42056
42109
|
const activeCourse = await requireActiveCourse();
|
|
42057
|
-
const
|
|
42058
|
-
|
|
42059
|
-
|
|
42060
|
-
|
|
42061
|
-
|
|
42062
|
-
|
|
42063
|
-
|
|
42064
|
-
|
|
42110
|
+
const cwdIsWorkspace = await isCwdWorkspace(process.cwd());
|
|
42111
|
+
let workspacePath;
|
|
42112
|
+
let directories;
|
|
42113
|
+
if (!opts.path && cwdIsWorkspace) {
|
|
42114
|
+
const resolvedCwd = await resolveCwdWorkspacePath(process.cwd());
|
|
42115
|
+
workspacePath = resolvedCwd ?? path9.join(process.cwd(), ".tostudy");
|
|
42116
|
+
await fs10.mkdir(workspacePath, { recursive: true });
|
|
42117
|
+
directories = ["exercises", "generated", "notes", "diagrams"];
|
|
42118
|
+
for (const dir of directories) {
|
|
42119
|
+
await fs10.mkdir(path9.join(workspacePath, dir), { recursive: true });
|
|
42120
|
+
}
|
|
42121
|
+
const configPath = path9.join(workspacePath, ".ana-config.json");
|
|
42122
|
+
await fs10.writeFile(
|
|
42123
|
+
configPath,
|
|
42124
|
+
JSON.stringify(
|
|
42125
|
+
{
|
|
42126
|
+
courseId: activeCourse.courseId,
|
|
42127
|
+
courseSlug: courseSlug(activeCourse.courseTitle),
|
|
42128
|
+
courseName: activeCourse.courseTitle,
|
|
42129
|
+
workspacePath,
|
|
42130
|
+
locale: "pt-BR",
|
|
42131
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42132
|
+
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
42133
|
+
},
|
|
42134
|
+
null,
|
|
42135
|
+
2
|
|
42136
|
+
),
|
|
42137
|
+
"utf-8"
|
|
42138
|
+
);
|
|
42139
|
+
} else {
|
|
42140
|
+
const basePath = opts.path ?? path9.join(os9.homedir(), "study");
|
|
42141
|
+
const result2 = await setupWorkspace({
|
|
42142
|
+
courseId: activeCourse.courseId,
|
|
42143
|
+
courseSlug: courseSlug(activeCourse.courseTitle),
|
|
42144
|
+
courseName: activeCourse.courseTitle,
|
|
42145
|
+
basePath,
|
|
42146
|
+
locale: "pt-BR"
|
|
42147
|
+
});
|
|
42148
|
+
workspacePath = result2.workspacePath;
|
|
42149
|
+
directories = result2.directories;
|
|
42150
|
+
}
|
|
42151
|
+
await setCourseWorkspacePath(activeCourse.courseId, workspacePath);
|
|
42152
|
+
const result = { workspacePath, directories };
|
|
42065
42153
|
if (opts.json) {
|
|
42066
42154
|
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
42067
42155
|
} else {
|
|
42068
42156
|
process.stdout.write(
|
|
42069
42157
|
`
|
|
42070
|
-
\u2705 Workspace criado em: ${
|
|
42158
|
+
\u2705 Workspace criado em: ${workspacePath}
|
|
42071
42159
|
|
|
42072
42160
|
Diret\xF3rios:
|
|
42073
|
-
${
|
|
42161
|
+
${directories.map((d) => ` \u{1F4C1} ${d}/`).join("\n")}
|
|
42074
42162
|
|
|
42075
42163
|
Pr\xF3ximo passo: tostudy export
|
|
42076
42164
|
`
|
|
@@ -42083,62 +42171,68 @@ Pr\xF3ximo passo: tostudy export
|
|
|
42083
42171
|
process.exit(1);
|
|
42084
42172
|
}
|
|
42085
42173
|
});
|
|
42086
|
-
workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
42174
|
+
workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path9.join(os9.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
42087
42175
|
try {
|
|
42088
42176
|
const activeCourse = await requireActiveCourse();
|
|
42089
|
-
const
|
|
42090
|
-
const
|
|
42091
|
-
|
|
42092
|
-
|
|
42093
|
-
|
|
42094
|
-
|
|
42095
|
-
|
|
42177
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
42178
|
+
const ws = await resolveEffectiveWorkspace(
|
|
42179
|
+
activeCourse.courseTitle,
|
|
42180
|
+
onboardingState?.workspacePath,
|
|
42181
|
+
process.cwd(),
|
|
42182
|
+
opts.path
|
|
42183
|
+
);
|
|
42184
|
+
if (!ws.found || !ws.workspacePath) {
|
|
42096
42185
|
process.stderr.write(
|
|
42097
42186
|
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
|
|
42098
42187
|
);
|
|
42099
42188
|
process.exit(1);
|
|
42100
42189
|
}
|
|
42101
|
-
const
|
|
42190
|
+
const workspacePath = ws.workspacePath;
|
|
42191
|
+
let configData = null;
|
|
42192
|
+
try {
|
|
42193
|
+
const raw = await fs10.readFile(path9.join(workspacePath, ".ana-config.json"), "utf-8");
|
|
42194
|
+
configData = JSON.parse(raw);
|
|
42195
|
+
} catch {
|
|
42196
|
+
configData = null;
|
|
42197
|
+
}
|
|
42198
|
+
const exercisesDir = path9.join(workspacePath, "exercises");
|
|
42102
42199
|
let exerciseCount = 0;
|
|
42103
42200
|
try {
|
|
42104
|
-
const moduleDirs = await
|
|
42201
|
+
const moduleDirs = await fs10.readdir(exercisesDir);
|
|
42105
42202
|
for (const modDir of moduleDirs) {
|
|
42106
|
-
const modPath =
|
|
42107
|
-
const stat = await
|
|
42203
|
+
const modPath = path9.join(exercisesDir, modDir);
|
|
42204
|
+
const stat = await fs10.stat(modPath);
|
|
42108
42205
|
if (stat.isDirectory()) {
|
|
42109
|
-
const lessonDirs = await
|
|
42206
|
+
const lessonDirs = await fs10.readdir(modPath);
|
|
42110
42207
|
for (const lessonDir of lessonDirs) {
|
|
42111
|
-
const lessonPath =
|
|
42112
|
-
const lstat = await
|
|
42208
|
+
const lessonPath = path9.join(modPath, lessonDir);
|
|
42209
|
+
const lstat = await fs10.stat(lessonPath);
|
|
42113
42210
|
if (lstat.isDirectory()) exerciseCount++;
|
|
42114
42211
|
}
|
|
42115
42212
|
}
|
|
42116
42213
|
}
|
|
42117
42214
|
} catch {
|
|
42118
42215
|
}
|
|
42119
|
-
const generatedDir =
|
|
42216
|
+
const generatedDir = path9.join(workspacePath, "generated");
|
|
42120
42217
|
let artifactCount = 0;
|
|
42121
42218
|
try {
|
|
42122
|
-
const files = await
|
|
42219
|
+
const files = await fs10.readdir(generatedDir);
|
|
42123
42220
|
artifactCount = files.length;
|
|
42124
42221
|
} catch {
|
|
42125
42222
|
}
|
|
42126
|
-
const diagramsDir =
|
|
42223
|
+
const diagramsDir = path9.join(workspacePath, "diagrams");
|
|
42127
42224
|
let diagramCount = 0;
|
|
42128
42225
|
try {
|
|
42129
|
-
const files = await
|
|
42226
|
+
const files = await fs10.readdir(diagramsDir);
|
|
42130
42227
|
diagramCount = files.length;
|
|
42131
42228
|
} catch {
|
|
42132
42229
|
}
|
|
42133
|
-
const
|
|
42134
|
-
|
|
42135
|
-
|
|
42136
|
-
await fs11.access(path10.join(vaultDir, ".ana-vault.json"));
|
|
42137
|
-
hasVault = true;
|
|
42138
|
-
} catch {
|
|
42139
|
-
}
|
|
42230
|
+
const slug = courseSlug(activeCourse.courseTitle);
|
|
42231
|
+
const foundVaultPath = await findExistingVault(workspacePath, slug);
|
|
42232
|
+
const hasVault = foundVaultPath !== null;
|
|
42140
42233
|
const status = {
|
|
42141
42234
|
workspacePath,
|
|
42235
|
+
workspaceSource: ws.source,
|
|
42142
42236
|
course: activeCourse.courseTitle,
|
|
42143
42237
|
courseId: activeCourse.courseId,
|
|
42144
42238
|
exercisesExtracted: exerciseCount,
|
|
@@ -42150,11 +42244,12 @@ Pr\xF3ximo passo: tostudy export
|
|
|
42150
42244
|
if (opts.json) {
|
|
42151
42245
|
process.stdout.write(JSON.stringify(status, null, 2) + "\n");
|
|
42152
42246
|
} else {
|
|
42247
|
+
const sourceLabel = ws.source === "cwd" ? " (pasta atual)" : ws.source === "stored" ? " (configurado)" : ws.source === "default" ? " (~/study/)" : "";
|
|
42153
42248
|
process.stdout.write(
|
|
42154
42249
|
[
|
|
42155
42250
|
"",
|
|
42156
42251
|
`\u{1F4DA} **${activeCourse.courseTitle}**`,
|
|
42157
|
-
`\u{1F4C1} ${workspacePath}`,
|
|
42252
|
+
`\u{1F4C1} ${workspacePath}${sourceLabel}`,
|
|
42158
42253
|
"",
|
|
42159
42254
|
` Exerc\xEDcios extra\xEDdos: ${exerciseCount}`,
|
|
42160
42255
|
` Artefatos exportados: ${artifactCount}`,
|
|
@@ -42177,8 +42272,9 @@ Pr\xF3ximo passo: tostudy export
|
|
|
42177
42272
|
|
|
42178
42273
|
// src/commands/export.ts
|
|
42179
42274
|
import { Command as Command17 } from "commander";
|
|
42180
|
-
import
|
|
42181
|
-
import
|
|
42275
|
+
import path10 from "node:path";
|
|
42276
|
+
import os10 from "node:os";
|
|
42277
|
+
import fs11 from "node:fs/promises";
|
|
42182
42278
|
var logger15, exportCommand;
|
|
42183
42279
|
var init_export = __esm({
|
|
42184
42280
|
"src/commands/export.ts"() {
|
|
@@ -42189,7 +42285,7 @@ var init_export = __esm({
|
|
|
42189
42285
|
init_session();
|
|
42190
42286
|
init_resolve();
|
|
42191
42287
|
logger15 = createLogger("cli:export");
|
|
42192
|
-
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",
|
|
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", path10.join(os10.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
42193
42289
|
try {
|
|
42194
42290
|
const session = await requireSession();
|
|
42195
42291
|
const activeCourse = await requireActiveCourse();
|
|
@@ -42199,10 +42295,57 @@ var init_export = __esm({
|
|
|
42199
42295
|
process.stderr.write("\u274C Nenhuma li\xE7\xE3o ativa. Execute 'tostudy start' primeiro.\n");
|
|
42200
42296
|
process.exit(1);
|
|
42201
42297
|
}
|
|
42202
|
-
const
|
|
42203
|
-
|
|
42298
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
42299
|
+
const ws = await resolveEffectiveWorkspace(
|
|
42300
|
+
activeCourse.courseTitle,
|
|
42301
|
+
onboardingState?.workspacePath,
|
|
42302
|
+
process.cwd(),
|
|
42303
|
+
opts.path
|
|
42304
|
+
);
|
|
42305
|
+
if (ws.found && ws.source === "cwd" && ws.workspacePath) {
|
|
42306
|
+
const configPath = path10.join(ws.workspacePath, ".ana-config.json");
|
|
42307
|
+
let hasConfig = false;
|
|
42308
|
+
try {
|
|
42309
|
+
await fs11.access(configPath);
|
|
42310
|
+
hasConfig = true;
|
|
42311
|
+
} catch {
|
|
42312
|
+
}
|
|
42313
|
+
if (!hasConfig) {
|
|
42314
|
+
const slug = courseSlug(activeCourse.courseTitle);
|
|
42315
|
+
logger15.info("Auto-initializing workspace", { workspacePath: ws.workspacePath });
|
|
42316
|
+
await fs11.mkdir(ws.workspacePath, { recursive: true });
|
|
42317
|
+
for (const dir of ["exercises", "generated", "notes", "diagrams"]) {
|
|
42318
|
+
await fs11.mkdir(path10.join(ws.workspacePath, dir), { recursive: true });
|
|
42319
|
+
}
|
|
42320
|
+
await fs11.writeFile(
|
|
42321
|
+
configPath,
|
|
42322
|
+
JSON.stringify(
|
|
42323
|
+
{
|
|
42324
|
+
courseId: activeCourse.courseId,
|
|
42325
|
+
courseSlug: slug,
|
|
42326
|
+
courseName: activeCourse.courseTitle,
|
|
42327
|
+
workspacePath: ws.workspacePath,
|
|
42328
|
+
locale: "pt-BR",
|
|
42329
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42330
|
+
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
42331
|
+
},
|
|
42332
|
+
null,
|
|
42333
|
+
2
|
|
42334
|
+
),
|
|
42335
|
+
"utf-8"
|
|
42336
|
+
);
|
|
42337
|
+
await setCourseWorkspacePath(activeCourse.courseId, ws.workspacePath);
|
|
42338
|
+
const isNamespaced = ws.workspacePath === path10.join(process.cwd(), ".tostudy");
|
|
42339
|
+
process.stderr.write(
|
|
42340
|
+
isNamespaced ? `\u2728 Workspace inicializado em .tostudy/ (isolado do projeto).
|
|
42341
|
+
` : `\u2728 Workspace inicializado nesta pasta.
|
|
42342
|
+
`
|
|
42343
|
+
);
|
|
42344
|
+
}
|
|
42345
|
+
}
|
|
42346
|
+
if (!ws.found || !ws.workspacePath) {
|
|
42204
42347
|
process.stderr.write(
|
|
42205
|
-
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup'
|
|
42348
|
+
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' ou rode 'tostudy select' desta pasta.\n"
|
|
42206
42349
|
);
|
|
42207
42350
|
process.exit(1);
|
|
42208
42351
|
}
|
|
@@ -42245,45 +42388,41 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
|
|
|
42245
42388
|
// src/commands/open.ts
|
|
42246
42389
|
import { Command as Command18 } from "commander";
|
|
42247
42390
|
import { execFile as execFile3 } from "node:child_process";
|
|
42248
|
-
import
|
|
42249
|
-
import
|
|
42250
|
-
import os9 from "node:os";
|
|
42251
|
-
async function findWorkspacePath(courseTitle, basePath) {
|
|
42252
|
-
const slug = courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
42253
|
-
const candidate = path12.join(basePath, slug);
|
|
42254
|
-
try {
|
|
42255
|
-
await fs12.access(path12.join(candidate, ".ana-config.json"));
|
|
42256
|
-
return candidate;
|
|
42257
|
-
} catch {
|
|
42258
|
-
return null;
|
|
42259
|
-
}
|
|
42260
|
-
}
|
|
42391
|
+
import path11 from "node:path";
|
|
42392
|
+
import os11 from "node:os";
|
|
42261
42393
|
var logger16, openCommand;
|
|
42262
42394
|
var init_open = __esm({
|
|
42263
42395
|
"src/commands/open.ts"() {
|
|
42264
42396
|
"use strict";
|
|
42265
42397
|
init_src();
|
|
42266
42398
|
init_session();
|
|
42399
|
+
init_resolve();
|
|
42267
42400
|
logger16 = createLogger("cli:open");
|
|
42268
|
-
openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
42401
|
+
openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path11.join(os11.homedir(), "study")).action(async (opts) => {
|
|
42269
42402
|
try {
|
|
42270
42403
|
const activeCourse = await requireActiveCourse();
|
|
42271
|
-
const
|
|
42272
|
-
|
|
42404
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
42405
|
+
const ws = await resolveEffectiveWorkspace(
|
|
42406
|
+
activeCourse.courseTitle,
|
|
42407
|
+
onboardingState?.workspacePath,
|
|
42408
|
+
process.cwd(),
|
|
42409
|
+
opts.path
|
|
42410
|
+
);
|
|
42411
|
+
if (!ws.found || !ws.workspacePath) {
|
|
42273
42412
|
process.stderr.write(
|
|
42274
42413
|
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
|
|
42275
42414
|
);
|
|
42276
42415
|
process.exit(1);
|
|
42277
42416
|
}
|
|
42278
42417
|
const editor = process.env["EDITOR"] ?? "code";
|
|
42279
|
-
execFile3(editor, [workspacePath], (err) => {
|
|
42418
|
+
execFile3(editor, [ws.workspacePath], (err) => {
|
|
42280
42419
|
if (err) {
|
|
42281
|
-
logger16.error("open failed", { editor, workspacePath });
|
|
42420
|
+
logger16.error("open failed", { editor, workspacePath: ws.workspacePath });
|
|
42282
42421
|
process.stderr.write(`\u274C Falha ao abrir: ${err.message}
|
|
42283
42422
|
`);
|
|
42284
42423
|
process.exit(1);
|
|
42285
42424
|
}
|
|
42286
|
-
process.stdout.write(`\u2705 Aberto em ${editor}: ${workspacePath}
|
|
42425
|
+
process.stdout.write(`\u2705 Aberto em ${editor}: ${ws.workspacePath}
|
|
42287
42426
|
`);
|
|
42288
42427
|
});
|
|
42289
42428
|
} catch (err) {
|
|
@@ -42314,24 +42453,24 @@ var init_types3 = __esm({
|
|
|
42314
42453
|
});
|
|
42315
42454
|
|
|
42316
42455
|
// ../../packages/tostudy-core/src/vault/write-vault.ts
|
|
42317
|
-
import
|
|
42318
|
-
import
|
|
42456
|
+
import fs12 from "node:fs/promises";
|
|
42457
|
+
import path12 from "node:path";
|
|
42319
42458
|
async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
|
|
42320
42459
|
for (const file2 of files) {
|
|
42321
|
-
const fullPath =
|
|
42322
|
-
await
|
|
42323
|
-
await
|
|
42460
|
+
const fullPath = path12.join(outputPath, file2.relativePath);
|
|
42461
|
+
await fs12.mkdir(path12.dirname(fullPath), { recursive: true });
|
|
42462
|
+
await fs12.writeFile(fullPath, file2.content, "utf-8");
|
|
42324
42463
|
}
|
|
42325
|
-
const vaultPath =
|
|
42464
|
+
const vaultPath = path12.join(outputPath, courseSlug2);
|
|
42326
42465
|
const marker = {
|
|
42327
42466
|
courseId,
|
|
42328
42467
|
courseSlug: courseSlug2,
|
|
42329
42468
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42330
42469
|
version: VAULT_MARKER_VERSION
|
|
42331
42470
|
};
|
|
42332
|
-
await
|
|
42333
|
-
await
|
|
42334
|
-
|
|
42471
|
+
await fs12.mkdir(vaultPath, { recursive: true });
|
|
42472
|
+
await fs12.writeFile(
|
|
42473
|
+
path12.join(vaultPath, VAULT_MARKER_FILENAME),
|
|
42335
42474
|
JSON.stringify(marker, null, 2),
|
|
42336
42475
|
"utf-8"
|
|
42337
42476
|
);
|
|
@@ -42356,9 +42495,9 @@ var init_vault = __esm({
|
|
|
42356
42495
|
|
|
42357
42496
|
// src/commands/vault.ts
|
|
42358
42497
|
import { Command as Command19 } from "commander";
|
|
42359
|
-
import
|
|
42360
|
-
import
|
|
42361
|
-
import
|
|
42498
|
+
import path13 from "node:path";
|
|
42499
|
+
import os12 from "node:os";
|
|
42500
|
+
import fs13 from "node:fs/promises";
|
|
42362
42501
|
var logger17, vaultCommand;
|
|
42363
42502
|
var init_vault2 = __esm({
|
|
42364
42503
|
"src/commands/vault.ts"() {
|
|
@@ -42368,17 +42507,31 @@ var init_vault2 = __esm({
|
|
|
42368
42507
|
init_courses();
|
|
42369
42508
|
init_http2();
|
|
42370
42509
|
init_session();
|
|
42510
|
+
init_resolve();
|
|
42371
42511
|
logger17 = createLogger("cli:vault");
|
|
42372
42512
|
vaultCommand = new Command19("vault").description("Gerenciar vault Obsidian do curso");
|
|
42373
|
-
vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
42513
|
+
vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path13.join(os12.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
42374
42514
|
try {
|
|
42375
42515
|
const session = await requireSession();
|
|
42376
42516
|
const activeCourse = await requireActiveCourse();
|
|
42377
42517
|
const driftWarning = await checkCourseDrift();
|
|
42378
42518
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
42379
|
-
const
|
|
42380
|
-
const
|
|
42381
|
-
|
|
42519
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
42520
|
+
const ws = await resolveEffectiveWorkspace(
|
|
42521
|
+
activeCourse.courseTitle,
|
|
42522
|
+
onboardingState?.workspacePath,
|
|
42523
|
+
process.cwd(),
|
|
42524
|
+
opts.path
|
|
42525
|
+
);
|
|
42526
|
+
if (!ws.found || !ws.workspacePath) {
|
|
42527
|
+
process.stderr.write(
|
|
42528
|
+
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
|
|
42529
|
+
);
|
|
42530
|
+
process.exit(1);
|
|
42531
|
+
}
|
|
42532
|
+
const slug = courseSlug(activeCourse.courseTitle);
|
|
42533
|
+
const workspacePath = ws.workspacePath;
|
|
42534
|
+
const vaultOutputPath = resolveVaultPath(workspacePath, slug);
|
|
42382
42535
|
const res = await fetch(`${session.apiUrl}/api/cli/vault/init`, {
|
|
42383
42536
|
method: "POST",
|
|
42384
42537
|
headers: {
|
|
@@ -42400,7 +42553,7 @@ var init_vault2 = __esm({
|
|
|
42400
42553
|
data.files,
|
|
42401
42554
|
vaultOutputPath,
|
|
42402
42555
|
activeCourse.courseId,
|
|
42403
|
-
|
|
42556
|
+
slug
|
|
42404
42557
|
);
|
|
42405
42558
|
logger17.info("Vault generated", {
|
|
42406
42559
|
courseId: activeCourse.courseId,
|
|
@@ -42441,25 +42594,36 @@ Para visualizar:
|
|
|
42441
42594
|
process.exit(1);
|
|
42442
42595
|
}
|
|
42443
42596
|
});
|
|
42444
|
-
vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace",
|
|
42597
|
+
vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path13.join(os12.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
|
|
42445
42598
|
try {
|
|
42446
42599
|
const session = await requireSession();
|
|
42447
42600
|
const activeCourse = await requireActiveCourse();
|
|
42448
42601
|
const driftWarning = await checkCourseDrift();
|
|
42449
42602
|
if (driftWarning) process.stderr.write(driftWarning + "\n");
|
|
42450
|
-
const
|
|
42451
|
-
const
|
|
42452
|
-
|
|
42453
|
-
|
|
42454
|
-
|
|
42603
|
+
const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
|
|
42604
|
+
const ws = await resolveEffectiveWorkspace(
|
|
42605
|
+
activeCourse.courseTitle,
|
|
42606
|
+
onboardingState?.workspacePath,
|
|
42607
|
+
process.cwd(),
|
|
42608
|
+
opts.path
|
|
42609
|
+
);
|
|
42610
|
+
if (!ws.found || !ws.workspacePath) {
|
|
42611
|
+
process.stderr.write(
|
|
42612
|
+
"\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
|
|
42613
|
+
);
|
|
42614
|
+
process.exit(1);
|
|
42615
|
+
}
|
|
42616
|
+
const slug = courseSlug(activeCourse.courseTitle);
|
|
42617
|
+
const vaultPath = await findExistingVault(ws.workspacePath, slug);
|
|
42618
|
+
if (!vaultPath) {
|
|
42455
42619
|
process.stderr.write("\u274C Vault n\xE3o encontrado. Execute 'tostudy vault init' primeiro.\n");
|
|
42456
42620
|
process.exit(1);
|
|
42457
42621
|
}
|
|
42458
42622
|
const data = createHttpProvider(session.apiUrl, session.token);
|
|
42459
42623
|
const deps = { data, logger: logger17 };
|
|
42460
42624
|
const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
|
|
42461
|
-
const markerPath =
|
|
42462
|
-
const markerRaw = await
|
|
42625
|
+
const markerPath = path13.join(vaultPath, ".ana-vault.json");
|
|
42626
|
+
const markerRaw = await fs13.readFile(markerPath, "utf-8");
|
|
42463
42627
|
const marker = JSON.parse(markerRaw);
|
|
42464
42628
|
marker.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
42465
42629
|
marker.progress = {
|
|
@@ -42467,10 +42631,10 @@ Para visualizar:
|
|
|
42467
42631
|
currentModule: progress3.currentModule.title,
|
|
42468
42632
|
currentLesson: progress3.currentLesson.title
|
|
42469
42633
|
};
|
|
42470
|
-
await
|
|
42471
|
-
const courseIndexPath =
|
|
42634
|
+
await fs13.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
|
|
42635
|
+
const courseIndexPath = path13.join(vaultPath, slug, "index.md");
|
|
42472
42636
|
try {
|
|
42473
|
-
let indexContent = await
|
|
42637
|
+
let indexContent = await fs13.readFile(courseIndexPath, "utf-8");
|
|
42474
42638
|
indexContent = indexContent.replace(/\n---\n\n> 📊 Progresso:.*\n/g, "");
|
|
42475
42639
|
const titleEnd = indexContent.indexOf("\n");
|
|
42476
42640
|
if (titleEnd !== -1) {
|
|
@@ -42481,7 +42645,7 @@ Para visualizar:
|
|
|
42481
42645
|
`;
|
|
42482
42646
|
indexContent = indexContent.slice(0, titleEnd) + banner + indexContent.slice(titleEnd);
|
|
42483
42647
|
}
|
|
42484
|
-
await
|
|
42648
|
+
await fs13.writeFile(courseIndexPath, indexContent, "utf-8");
|
|
42485
42649
|
} catch {
|
|
42486
42650
|
}
|
|
42487
42651
|
const syncedAt = marker.lastSyncedAt;
|
|
@@ -42519,15 +42683,152 @@ Para visualizar:
|
|
|
42519
42683
|
}
|
|
42520
42684
|
});
|
|
42521
42685
|
|
|
42686
|
+
// src/commands/profile.ts
|
|
42687
|
+
import { Command as Command20 } from "commander";
|
|
42688
|
+
var profileCommand;
|
|
42689
|
+
var init_profile = __esm({
|
|
42690
|
+
"src/commands/profile.ts"() {
|
|
42691
|
+
"use strict";
|
|
42692
|
+
init_session();
|
|
42693
|
+
profileCommand = new Command20("profile").description("Show your learner profile for the active course").option("--json", "Output structured JSON").action(async (opts) => {
|
|
42694
|
+
const activeCourse = await requireActiveCourse();
|
|
42695
|
+
const onboarding = await getCourseOnboardingState(activeCourse.courseId);
|
|
42696
|
+
const profile = onboarding?.learnerProfile ?? await getUserProfile();
|
|
42697
|
+
if (opts.json) {
|
|
42698
|
+
process.stdout.write(
|
|
42699
|
+
JSON.stringify(
|
|
42700
|
+
{
|
|
42701
|
+
courseId: activeCourse.courseId,
|
|
42702
|
+
courseTitle: activeCourse.courseTitle,
|
|
42703
|
+
profile: profile ?? null,
|
|
42704
|
+
source: onboarding?.learnerProfile ? "course" : profile ? "user" : "none"
|
|
42705
|
+
},
|
|
42706
|
+
null,
|
|
42707
|
+
2
|
|
42708
|
+
) + "\n"
|
|
42709
|
+
);
|
|
42710
|
+
return;
|
|
42711
|
+
}
|
|
42712
|
+
if (!profile) {
|
|
42713
|
+
process.stdout.write("\n Nenhum perfil configurado.\n");
|
|
42714
|
+
process.stdout.write(" \u2192 Rode `tostudy init` para configurar seu perfil.\n\n");
|
|
42715
|
+
return;
|
|
42716
|
+
}
|
|
42717
|
+
const levelLabels = {
|
|
42718
|
+
beginner: "Iniciante",
|
|
42719
|
+
intermediate: "Intermedi\xE1rio",
|
|
42720
|
+
advanced: "Avan\xE7ado"
|
|
42721
|
+
};
|
|
42722
|
+
const source = onboarding?.learnerProfile ? "curso" : "perfil global";
|
|
42723
|
+
process.stdout.write(`
|
|
42724
|
+
Perfil \u2014 ${activeCourse.courseTitle}
|
|
42725
|
+
|
|
42726
|
+
`);
|
|
42727
|
+
process.stdout.write(` Segmento: ${profile.segment}
|
|
42728
|
+
`);
|
|
42729
|
+
process.stdout.write(` Empresa: ${profile.company}
|
|
42730
|
+
`);
|
|
42731
|
+
process.stdout.write(` Produtos: ${profile.productsOrServices}
|
|
42732
|
+
`);
|
|
42733
|
+
process.stdout.write(` Regi\xE3o: ${profile.region}
|
|
42734
|
+
`);
|
|
42735
|
+
process.stdout.write(` Equipe: ${profile.team}
|
|
42736
|
+
`);
|
|
42737
|
+
process.stdout.write(` Objetivo: ${profile.goal}
|
|
42738
|
+
`);
|
|
42739
|
+
process.stdout.write(
|
|
42740
|
+
` N\xEDvel: ${levelLabels[profile.learnerLevel] ?? profile.learnerLevel}
|
|
42741
|
+
`
|
|
42742
|
+
);
|
|
42743
|
+
process.stdout.write(` Contexto real: ${profile.adaptToRealContext ? "Sim" : "N\xE3o"}
|
|
42744
|
+
`);
|
|
42745
|
+
process.stdout.write(`
|
|
42746
|
+
Fonte: ${source}
|
|
42747
|
+
|
|
42748
|
+
`);
|
|
42749
|
+
});
|
|
42750
|
+
}
|
|
42751
|
+
});
|
|
42752
|
+
|
|
42753
|
+
// src/commands/sync.ts
|
|
42754
|
+
import { Command as Command21 } from "commander";
|
|
42755
|
+
var logger18, syncCommand;
|
|
42756
|
+
var init_sync = __esm({
|
|
42757
|
+
"src/commands/sync.ts"() {
|
|
42758
|
+
"use strict";
|
|
42759
|
+
init_src();
|
|
42760
|
+
init_courses();
|
|
42761
|
+
init_http2();
|
|
42762
|
+
init_session();
|
|
42763
|
+
init_formatter();
|
|
42764
|
+
init_instruction_files();
|
|
42765
|
+
init_status();
|
|
42766
|
+
logger18 = createLogger("cli:sync");
|
|
42767
|
+
syncCommand = new Command21("sync").description("Regenerate instruction files with updated progress").option("--json", "Output structured JSON").action(async (opts) => {
|
|
42768
|
+
try {
|
|
42769
|
+
const session = await requireSession();
|
|
42770
|
+
const activeCourse = await requireActiveCourse();
|
|
42771
|
+
const data = createHttpProvider(session.apiUrl, session.token);
|
|
42772
|
+
const deps = { data, logger: logger18 };
|
|
42773
|
+
const [courses3, progressData] = await Promise.all([
|
|
42774
|
+
listCourses({ userId: session.userId }, deps),
|
|
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
|
|
42797
|
+
);
|
|
42798
|
+
if (opts.json) {
|
|
42799
|
+
output(
|
|
42800
|
+
{
|
|
42801
|
+
status: "ok",
|
|
42802
|
+
courseId: activeCourse.courseId,
|
|
42803
|
+
slug,
|
|
42804
|
+
progress: progressData?.coursePercent ?? matchedCourse.progress ?? 0
|
|
42805
|
+
},
|
|
42806
|
+
{ json: true }
|
|
42807
|
+
);
|
|
42808
|
+
} else {
|
|
42809
|
+
const progress3 = progressData?.coursePercent ?? matchedCourse.progress ?? 0;
|
|
42810
|
+
output(`\u2713 Instru\xE7\xF5es sincronizadas para "${matchedCourse.title}" (${progress3}%)`, {
|
|
42811
|
+
json: false
|
|
42812
|
+
});
|
|
42813
|
+
}
|
|
42814
|
+
} catch (err) {
|
|
42815
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
42816
|
+
if (msg.includes("process.exit")) return;
|
|
42817
|
+
error(msg);
|
|
42818
|
+
}
|
|
42819
|
+
});
|
|
42820
|
+
}
|
|
42821
|
+
});
|
|
42822
|
+
|
|
42522
42823
|
// src/cli.ts
|
|
42523
42824
|
var cli_exports = {};
|
|
42524
42825
|
__export(cli_exports, {
|
|
42525
42826
|
CLI_VERSION: () => CLI_VERSION,
|
|
42526
42827
|
createProgram: () => createProgram
|
|
42527
42828
|
});
|
|
42528
|
-
import { Command as
|
|
42829
|
+
import { Command as Command22 } from "commander";
|
|
42529
42830
|
function createProgram() {
|
|
42530
|
-
const program2 = new
|
|
42831
|
+
const program2 = new Command22();
|
|
42531
42832
|
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");
|
|
42532
42833
|
program2.addCommand(setupCommand);
|
|
42533
42834
|
program2.addCommand(doctorCommand);
|
|
@@ -42544,7 +42845,9 @@ function createProgram() {
|
|
|
42544
42845
|
program2.addCommand(hintCommand);
|
|
42545
42846
|
program2.addCommand(validateCommand);
|
|
42546
42847
|
program2.addCommand(menuCommand);
|
|
42848
|
+
program2.addCommand(profileCommand);
|
|
42547
42849
|
program2.addCommand(workspaceCommand);
|
|
42850
|
+
program2.addCommand(syncCommand);
|
|
42548
42851
|
program2.addCommand(exportCommand);
|
|
42549
42852
|
program2.addCommand(openCommand);
|
|
42550
42853
|
program2.addCommand(vaultCommand);
|
|
@@ -42573,7 +42876,9 @@ var init_cli = __esm({
|
|
|
42573
42876
|
init_export();
|
|
42574
42877
|
init_open();
|
|
42575
42878
|
init_vault2();
|
|
42576
|
-
|
|
42879
|
+
init_profile();
|
|
42880
|
+
init_sync();
|
|
42881
|
+
CLI_VERSION = true ? "0.8.0" : "0.7.1";
|
|
42577
42882
|
}
|
|
42578
42883
|
});
|
|
42579
42884
|
|