@tostudy-ai/cli 0.7.4 → 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 CHANGED
@@ -2280,6 +2280,7 @@ var init_mcp_setup = __esm({
2280
2280
  // src/workspace/instruction-files.ts
2281
2281
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
2282
2282
  import { join as join2 } from "node:path";
2283
+ import os4 from "node:os";
2283
2284
  function slugify(title) {
2284
2285
  return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
2285
2286
  }
@@ -2395,19 +2396,41 @@ ${rules.map((r, i) => `${i + 1}. ${r}`).join("\n")}`);
2395
2396
  | Aluno perdido / sem saber o que fazer | Rodar \`tostudy progress\` e resumir estado atual |
2396
2397
  | "Stop hook error" / "ECONNREFUSED" | Ignorar \u2014 problema de configura\xE7\xE3o do IDE, n\xE3o do ToStudy |`);
2397
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
+ }
2398
2419
  sections.push(`## Workspace
2399
2420
 
2400
- O workspace local organiza os arquivos do curso:
2421
+ ${locationHint}
2401
2422
 
2402
2423
  \`\`\`
2403
- ~/study/{slug}/
2424
+ ${displayPath}/
2404
2425
  \u251C\u2500\u2500 exercises/{m\xF3dulo}/{li\xE7\xE3o}/ \u2190 Exerc\xEDcios extra\xEDdos
2405
2426
  \u251C\u2500\u2500 generated/ \u2190 Artefatos gerados
2406
- \u2514\u2500\u2500 diagrams/ \u2190 Diagramas
2427
+ \u251C\u2500\u2500 diagrams/ \u2190 Diagramas
2428
+ \u2514\u2500\u2500 ${vaultRelativeHint}
2407
2429
  \`\`\`
2408
2430
 
2409
2431
  Comandos \xFAteis:
2410
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")
2411
2434
  - \`tostudy open\` \u2014 Abrir workspace no editor
2412
2435
  - \`tostudy workspace status\` \u2014 Verificar estado do workspace`);
2413
2436
  }
@@ -2582,10 +2605,10 @@ FIM DO M\xD3DULO:
2582
2605
 
2583
2606
  ## Workspace
2584
2607
 
2585
- Quando o aluno precisar de um workspace local para exerc\xEDcios:
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.
2586
2610
 
2587
- - \`tostudy workspace setup\` \u2014 Cria diret\xF3rios em ~/study/{slug}/
2588
- - \`tostudy export\` \u2014 Extrai exerc\xEDcio para o workspace
2611
+ - \`tostudy export\` \u2014 Extrai exerc\xEDcio para o workspace (esta pasta ou ~/study/{slug}/)
2589
2612
  - \`tostudy open\` \u2014 Abre workspace no editor
2590
2613
  - \`tostudy workspace status\` \u2014 Verifica estado
2591
2614
 
@@ -2747,10 +2770,14 @@ async function runSetup(opts, deps = defaultDeps) {
2747
2770
  const activeCourse = await deps.getActiveCourse();
2748
2771
  const hasClaudeCmd = existsSync3(join3(cwd, ".claude", "commands", "tostudy.md"));
2749
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"));
2750
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");
2751
2776
  deps.log(" Setup completo!\n");
2752
2777
  if (activeCourse) {
2753
2778
  deps.log(` Curso ativo: ${activeCourse.courseTitle}`);
2779
+ } else {
2780
+ deps.log(" Nenhum curso ativo \u2014 rode: tostudy courses");
2754
2781
  }
2755
2782
  if (hasClaudeCmd) {
2756
2783
  deps.log(" \u2192 No Claude Code, digite: /tostudy");
@@ -2759,6 +2786,9 @@ async function runSetup(opts, deps = defaultDeps) {
2759
2786
  } else {
2760
2787
  deps.log(" \u2192 Abra seu IDE e o tutor estar\xE1 dispon\xEDvel");
2761
2788
  }
2789
+ if (hasClaudeIDE && !hasMcpConfig && !opts.mcp) {
2790
+ deps.log("\n \u{1F4A1} Dica: rode tostudy setup --mcp para habilitar ferramentas avan\xE7adas");
2791
+ }
2762
2792
  deps.log("");
2763
2793
  }
2764
2794
  async function runSetupMcpSubcommand() {
@@ -2813,12 +2843,12 @@ __export(update_checker_exports, {
2813
2843
  });
2814
2844
  import fs4 from "node:fs";
2815
2845
  import path4 from "node:path";
2816
- import os4 from "node:os";
2846
+ import os5 from "node:os";
2817
2847
  function getConfigDir2() {
2818
2848
  if (process.platform === "linux" && process.env["XDG_CONFIG_HOME"]) {
2819
2849
  return path4.join(process.env["XDG_CONFIG_HOME"], "tostudy");
2820
2850
  }
2821
- return path4.join(os4.homedir(), ".tostudy");
2851
+ return path4.join(os5.homedir(), ".tostudy");
2822
2852
  }
2823
2853
  function readCache() {
2824
2854
  try {
@@ -3044,8 +3074,112 @@ var init_courses2 = __esm({
3044
3074
  }
3045
3075
  });
3046
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
+
3047
3180
  // src/commands/select.ts
3048
3181
  import { Command as Command6 } from "commander";
3182
+ import os7 from "node:os";
3049
3183
  var logger5, selectCommand;
3050
3184
  var init_select = __esm({
3051
3185
  "src/commands/select.ts"() {
@@ -3056,6 +3190,7 @@ var init_select = __esm({
3056
3190
  init_session();
3057
3191
  init_formatter();
3058
3192
  init_instruction_files();
3193
+ init_status();
3059
3194
  logger5 = createLogger("cli:select");
3060
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) => {
3061
3196
  try {
@@ -3088,6 +3223,12 @@ var init_select = __esm({
3088
3223
  courseTags: matched?.tags,
3089
3224
  courseLevel: matched?.level
3090
3225
  });
3226
+ const activeCourseForStatus = {
3227
+ courseId: detail.courseId,
3228
+ courseTitle: detail.courseTitle,
3229
+ enrollmentId
3230
+ };
3231
+ const onboarding = await getCourseOnboardingStatus(activeCourseForStatus);
3091
3232
  let courseSlug2 = "";
3092
3233
  try {
3093
3234
  courseSlug2 = generateInstructionFiles({
@@ -3096,7 +3237,9 @@ var init_select = __esm({
3096
3237
  progress: detail.progress,
3097
3238
  moduleCount: detail.moduleCount,
3098
3239
  lessonCount: detail.lessonCount,
3099
- courseDescription: detail.courseDescription
3240
+ courseDescription: detail.courseDescription,
3241
+ workspaceReady: onboarding.workspaceReady,
3242
+ workspacePath: onboarding.workspacePath ?? void 0
3100
3243
  });
3101
3244
  } catch (err) {
3102
3245
  logger5.warn("Failed to generate instruction files", {
@@ -3104,13 +3247,36 @@ var init_select = __esm({
3104
3247
  });
3105
3248
  }
3106
3249
  if (opts.json) {
3107
- output({ ...detail, enrollmentId, courseSlug: courseSlug2 }, { json: true });
3250
+ output(
3251
+ {
3252
+ ...detail,
3253
+ enrollmentId,
3254
+ courseSlug: courseSlug2,
3255
+ workspacePath: onboarding.workspacePath,
3256
+ workspaceSource: onboarding.workspaceSource
3257
+ },
3258
+ { json: true }
3259
+ );
3108
3260
  } else {
3109
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
+ }
3110
3275
  output(
3111
3276
  [
3112
3277
  `\u2713 Curso ativado: ${detail.courseTitle}`,
3113
3278
  ` Progresso: ${detail.progress}% | ${detail.moduleCount} m\xF3dulos | ${detail.lessonCount} li\xE7\xF5es`,
3279
+ wsLine,
3114
3280
  "",
3115
3281
  " Arquivos de contexto criados para seu assistente AI.",
3116
3282
  "",
@@ -4709,7 +4875,7 @@ var init_query_promise = __esm({
4709
4875
  function mapResultRow(columns, row, joinsNotNullableMap) {
4710
4876
  const nullifyMap = {};
4711
4877
  const result = columns.reduce(
4712
- (result2, { path: path15, field }, columnIndex) => {
4878
+ (result2, { path: path14, field }, columnIndex) => {
4713
4879
  let decoder;
4714
4880
  if (is(field, Column)) {
4715
4881
  decoder = field;
@@ -4721,8 +4887,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
4721
4887
  decoder = field.sql.decoder;
4722
4888
  }
4723
4889
  let node = result2;
4724
- for (const [pathChunkIndex, pathChunk] of path15.entries()) {
4725
- if (pathChunkIndex < path15.length - 1) {
4890
+ for (const [pathChunkIndex, pathChunk] of path14.entries()) {
4891
+ if (pathChunkIndex < path14.length - 1) {
4726
4892
  if (!(pathChunk in node)) {
4727
4893
  node[pathChunk] = {};
4728
4894
  }
@@ -4730,8 +4896,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
4730
4896
  } else {
4731
4897
  const rawValue = row[columnIndex];
4732
4898
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
4733
- if (joinsNotNullableMap && is(field, Column) && path15.length === 2) {
4734
- const objectName = path15[0];
4899
+ if (joinsNotNullableMap && is(field, Column) && path14.length === 2) {
4900
+ const objectName = path14[0];
4735
4901
  if (!(objectName in nullifyMap)) {
4736
4902
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
4737
4903
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -8678,13 +8844,13 @@ function Subscribe(postgres2, options) {
8678
8844
  }
8679
8845
  }
8680
8846
  function handle(a, b2) {
8681
- const path15 = b2.relation.schema + "." + b2.relation.table;
8847
+ const path14 = b2.relation.schema + "." + b2.relation.table;
8682
8848
  call("*", a, b2);
8683
- call("*:" + path15, a, b2);
8684
- b2.relation.keys.length && call("*:" + path15 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
8849
+ call("*:" + path14, a, b2);
8850
+ b2.relation.keys.length && call("*:" + path14 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
8685
8851
  call(b2.command, a, b2);
8686
- call(b2.command + ":" + path15, a, b2);
8687
- b2.relation.keys.length && call(b2.command + ":" + path15 + "=" + b2.relation.keys.map((x2) => a[x2.name]), a, b2);
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);
8688
8854
  }
8689
8855
  function pong() {
8690
8856
  const x2 = Buffer.alloc(34);
@@ -8797,8 +8963,8 @@ function parseEvent(x) {
8797
8963
  const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || [];
8798
8964
  if (!xs)
8799
8965
  throw new Error("Malformed subscribe pattern: " + x);
8800
- const [, command, path15, key] = xs;
8801
- return (command || "*") + (path15 ? ":" + (path15.indexOf(".") === -1 ? "public." + path15 : path15) : "") + (key ? "=" + key : "");
8966
+ const [, command, path14, key] = xs;
8967
+ return (command || "*") + (path14 ? ":" + (path14.indexOf(".") === -1 ? "public." + path14 : path14) : "") + (key ? "=" + key : "");
8802
8968
  }
8803
8969
  var noop2;
8804
8970
  var init_subscribe = __esm({
@@ -8879,8 +9045,8 @@ var init_large = __esm({
8879
9045
  });
8880
9046
 
8881
9047
  // ../../node_modules/postgres/src/index.js
8882
- import os5 from "os";
8883
- import fs5 from "fs";
9048
+ import os8 from "os";
9049
+ import fs6 from "fs";
8884
9050
  function Postgres(a, b2) {
8885
9051
  const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
8886
9052
  let ending = false;
@@ -8936,10 +9102,10 @@ function Postgres(a, b2) {
8936
9102
  });
8937
9103
  return query;
8938
9104
  }
8939
- function file2(path15, args = [], options2 = {}) {
9105
+ function file2(path14, args = [], options2 = {}) {
8940
9106
  arguments.length === 2 && !Array.isArray(args) && (options2 = args, args = []);
8941
9107
  const query = new Query([], args, (query2) => {
8942
- fs5.readFile(path15, "utf8", (err, string4) => {
9108
+ fs6.readFile(path14, "utf8", (err, string4) => {
8943
9109
  if (err)
8944
9110
  return query2.reject(err);
8945
9111
  query2.strings = [string4];
@@ -9257,7 +9423,7 @@ function parseUrl(url2) {
9257
9423
  }
9258
9424
  function osUsername() {
9259
9425
  try {
9260
- return os5.userInfo().username;
9426
+ return os8.userInfo().username;
9261
9427
  } catch (_) {
9262
9428
  return process.env.USERNAME || process.env.USER || process.env.LOGNAME;
9263
9429
  }
@@ -13518,12 +13684,12 @@ var init_session3 = __esm({
13518
13684
  init_tracing();
13519
13685
  init_utils();
13520
13686
  PostgresJsPreparedQuery = class extends PgPreparedQuery {
13521
- constructor(client, queryString, params, logger18, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
13687
+ constructor(client, queryString, params, logger19, cache, queryMetadata, cacheConfig, fields, _isResponseInArrayMode, customResultMapper) {
13522
13688
  super({ sql: queryString, params }, cache, queryMetadata, cacheConfig);
13523
13689
  this.client = client;
13524
13690
  this.queryString = queryString;
13525
13691
  this.params = params;
13526
- this.logger = logger18;
13692
+ this.logger = logger19;
13527
13693
  this.fields = fields;
13528
13694
  this._isResponseInArrayMode = _isResponseInArrayMode;
13529
13695
  this.customResultMapper = customResultMapper;
@@ -13664,11 +13830,11 @@ function construct(client, config2 = {}) {
13664
13830
  client.options.serializers["114"] = transparentParser;
13665
13831
  client.options.serializers["3802"] = transparentParser;
13666
13832
  const dialect = new PgDialect({ casing: config2.casing });
13667
- let logger18;
13833
+ let logger19;
13668
13834
  if (config2.logger === true) {
13669
- logger18 = new DefaultLogger();
13835
+ logger19 = new DefaultLogger();
13670
13836
  } else if (config2.logger !== false) {
13671
- logger18 = config2.logger;
13837
+ logger19 = config2.logger;
13672
13838
  }
13673
13839
  let schema;
13674
13840
  if (config2.schema) {
@@ -13682,7 +13848,7 @@ function construct(client, config2 = {}) {
13682
13848
  tableNamesMap: tablesConfig.tableNamesMap
13683
13849
  };
13684
13850
  }
13685
- const session = new PostgresJsSession(client, dialect, schema, { logger: logger18, cache: config2.cache });
13851
+ const session = new PostgresJsSession(client, dialect, schema, { logger: logger19, cache: config2.cache });
13686
13852
  const db2 = new PostgresJsDatabase(dialect, session, schema);
13687
13853
  db2.$client = client;
13688
13854
  db2.$cache = config2.cache;
@@ -13884,7 +14050,7 @@ __export(util_exports, {
13884
14050
  required: () => required,
13885
14051
  safeExtend: () => safeExtend,
13886
14052
  shallowClone: () => shallowClone,
13887
- slugify: () => slugify3,
14053
+ slugify: () => slugify2,
13888
14054
  stringifyPrimitive: () => stringifyPrimitive,
13889
14055
  uint8ArrayToBase64: () => uint8ArrayToBase64,
13890
14056
  uint8ArrayToBase64url: () => uint8ArrayToBase64url,
@@ -13997,10 +14163,10 @@ function mergeDefs(...defs) {
13997
14163
  function cloneDef(schema) {
13998
14164
  return mergeDefs(schema._zod.def);
13999
14165
  }
14000
- function getElementAtPath(obj, path15) {
14001
- if (!path15)
14166
+ function getElementAtPath(obj, path14) {
14167
+ if (!path14)
14002
14168
  return obj;
14003
- return path15.reduce((acc, key) => acc?.[key], obj);
14169
+ return path14.reduce((acc, key) => acc?.[key], obj);
14004
14170
  }
14005
14171
  function promiseAllObject(promisesObj) {
14006
14172
  const keys = Object.keys(promisesObj);
@@ -14024,7 +14190,7 @@ function randomString(length = 10) {
14024
14190
  function esc(str) {
14025
14191
  return JSON.stringify(str);
14026
14192
  }
14027
- function slugify3(input2) {
14193
+ function slugify2(input2) {
14028
14194
  return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
14029
14195
  }
14030
14196
  function isObject(data) {
@@ -14312,11 +14478,11 @@ function aborted(x, startIndex = 0) {
14312
14478
  }
14313
14479
  return false;
14314
14480
  }
14315
- function prefixIssues(path15, issues) {
14481
+ function prefixIssues(path14, issues) {
14316
14482
  return issues.map((iss) => {
14317
14483
  var _a2;
14318
14484
  (_a2 = iss).path ?? (_a2.path = []);
14319
- iss.path.unshift(path15);
14485
+ iss.path.unshift(path14);
14320
14486
  return iss;
14321
14487
  });
14322
14488
  }
@@ -14558,7 +14724,7 @@ function formatError(error49, mapper = (issue2) => issue2.message) {
14558
14724
  }
14559
14725
  function treeifyError(error49, mapper = (issue2) => issue2.message) {
14560
14726
  const result = { errors: [] };
14561
- const processError = (error50, path15 = []) => {
14727
+ const processError = (error50, path14 = []) => {
14562
14728
  var _a2, _b;
14563
14729
  for (const issue2 of error50.issues) {
14564
14730
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -14568,7 +14734,7 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
14568
14734
  } else if (issue2.code === "invalid_element") {
14569
14735
  processError({ issues: issue2.issues }, issue2.path);
14570
14736
  } else {
14571
- const fullpath = [...path15, ...issue2.path];
14737
+ const fullpath = [...path14, ...issue2.path];
14572
14738
  if (fullpath.length === 0) {
14573
14739
  result.errors.push(mapper(issue2));
14574
14740
  continue;
@@ -14600,8 +14766,8 @@ function treeifyError(error49, mapper = (issue2) => issue2.message) {
14600
14766
  }
14601
14767
  function toDotPath(_path) {
14602
14768
  const segs = [];
14603
- const path15 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
14604
- for (const seg of path15) {
14769
+ const path14 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
14770
+ for (const seg of path14) {
14605
14771
  if (typeof seg === "number")
14606
14772
  segs.push(`[${seg}]`);
14607
14773
  else if (typeof seg === "symbol")
@@ -24158,7 +24324,7 @@ function _toUpperCase() {
24158
24324
  }
24159
24325
  // @__NO_SIDE_EFFECTS__
24160
24326
  function _slugify() {
24161
- return /* @__PURE__ */ _overwrite((input2) => slugify3(input2));
24327
+ return /* @__PURE__ */ _overwrite((input2) => slugify2(input2));
24162
24328
  }
24163
24329
  // @__NO_SIDE_EFFECTS__
24164
24330
  function _array(Class2, element, params) {
@@ -27295,13 +27461,13 @@ function resolveRef(ref, ctx) {
27295
27461
  if (!ref.startsWith("#")) {
27296
27462
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
27297
27463
  }
27298
- const path15 = ref.slice(1).split("/").filter(Boolean);
27299
- if (path15.length === 0) {
27464
+ const path14 = ref.slice(1).split("/").filter(Boolean);
27465
+ if (path14.length === 0) {
27300
27466
  return ctx.rootSchema;
27301
27467
  }
27302
27468
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
27303
- if (path15[0] === defsKey) {
27304
- const key = path15[1];
27469
+ if (path14[0] === defsKey) {
27470
+ const key = path14[1];
27305
27471
  if (!key || !ctx.defs[key]) {
27306
27472
  throw new Error(`Reference not found: ${ref}`);
27307
27473
  }
@@ -28490,7 +28656,11 @@ var init_courses3 = __esm({
28490
28656
  // Learning Path Graph: Reference to migrated graph (null if not migrated)
28491
28657
  migratedToGraphId: uuid("migrated_to_graph_id"),
28492
28658
  // Study Channel Configuration: which channels are available for this course
28493
- 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 })
28494
28664
  },
28495
28665
  (table) => ({
28496
28666
  creatorIdIdx: index("courses_creator_id_idx").on(table.creatorId),
@@ -32926,29 +33096,32 @@ var init_validation_attempts = __esm({
32926
33096
  init_users();
32927
33097
  init_enrollments();
32928
33098
  init_courses3();
32929
- validationAttempts2 = pgTable("validation_attempts", {
32930
- id: uuid("id").defaultRandom().primaryKey(),
32931
- lessonId: uuid("lesson_id").references(() => lessons.id, { onDelete: "cascade" }).notNull(),
32932
- userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
32933
- passed: boolean("passed").notNull(),
32934
- score: text("score"),
32935
- // Store as text to support percentages like "85%"
32936
- feedback: text("feedback"),
32937
- missingCriteria: jsonb("missing_criteria").$type(),
32938
- attemptedAt: timestamp("attempted_at").defaultNow().notNull(),
32939
- // v2 — validation persistence & learning analytics
32940
- enrollmentId: uuid("enrollment_id").references(() => enrollments.id, { onDelete: "cascade" }),
32941
- courseId: uuid("course_id").references(() => courses.id, { onDelete: "cascade" }),
32942
- exerciseIndex: integer("exercise_index"),
32943
- solution: text("solution"),
32944
- criteriaResults: jsonb("criteria_results").$type(),
32945
- source: text("source").$type(),
32946
- duration: integer("duration")
32947
- }, (table) => [
32948
- index("va_user_lesson_exercise_idx").on(table.userId, table.lessonId, table.exerciseIndex),
32949
- index("va_course_lesson_passed_idx").on(table.courseId, table.lessonId, table.passed),
32950
- index("va_user_course_attempted_idx").on(table.userId, table.courseId, table.attemptedAt)
32951
- ]);
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
+ );
32952
33125
  }
32953
33126
  });
32954
33127
 
@@ -35131,77 +35304,6 @@ var init_course_variants = __esm({
35131
35304
  }
35132
35305
  });
35133
35306
 
35134
- // ../../packages/database/src/schema/_archived/user-credits.ts
35135
- var transactionTypeEnum, userCredits2, creditTransactions2;
35136
- var init_user_credits = __esm({
35137
- "../../packages/database/src/schema/_archived/user-credits.ts"() {
35138
- "use strict";
35139
- init_pg_core();
35140
- init_users();
35141
- transactionTypeEnum = pgEnum("transaction_type", [
35142
- "initial_grant",
35143
- // Credito inicial de $5
35144
- "purchase",
35145
- // Compra de creditos
35146
- "consumption",
35147
- // Consumo por uso de IA
35148
- "refund",
35149
- // Reembolso
35150
- "adjustment"
35151
- // Ajuste manual admin
35152
- ]);
35153
- userCredits2 = pgTable(
35154
- "user_credits",
35155
- {
35156
- id: uuid("id").primaryKey().defaultRandom(),
35157
- userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }).notNull().unique(),
35158
- /** Current balance in USD */
35159
- balance: decimal("balance", { precision: 10, scale: 4 }).notNull().default("0"),
35160
- /** Total consumed in USD (absolute value, always positive) */
35161
- totalConsumed: decimal("total_consumed", { precision: 10, scale: 4 }).notNull().default("0"),
35162
- /** Total purchased in USD */
35163
- totalPurchased: decimal("total_purchased", { precision: 10, scale: 4 }).notNull().default("0"),
35164
- createdAt: timestamp("created_at").defaultNow().notNull(),
35165
- updatedAt: timestamp("updated_at").defaultNow().notNull()
35166
- },
35167
- (table) => ({
35168
- userIdIdx: index("user_credits_user_id_idx").on(table.userId)
35169
- })
35170
- );
35171
- creditTransactions2 = pgTable(
35172
- "credit_transactions",
35173
- {
35174
- id: uuid("id").primaryKey().defaultRandom(),
35175
- userId: uuid("user_id").references(() => users.id, { onDelete: "restrict" }).notNull(),
35176
- type: transactionTypeEnum("type").notNull(),
35177
- /** Amount in USD (negative for consumption, positive for purchase/grant) */
35178
- amount: decimal("amount", { precision: 10, scale: 4 }).notNull(),
35179
- /** Balance after this transaction in USD */
35180
- balanceAfter: decimal("balance_after", { precision: 10, scale: 4 }).notNull(),
35181
- description: text("description"),
35182
- metadata: text("metadata"),
35183
- // JSON com detalhes (tokens, modelo, etc)
35184
- // Granular usage tracking columns
35185
- operation: varchar("operation", { length: 50 }),
35186
- // 'brainstorm_message', 'generation_outline', etc.
35187
- model: varchar("model", { length: 100 }),
35188
- // 'claude-sonnet-4-5-20250929'
35189
- inputTokens: integer("input_tokens"),
35190
- outputTokens: integer("output_tokens"),
35191
- latencyMs: integer("latency_ms"),
35192
- // DB FK to course_proposals preserved in migration; Drizzle ref removed for schema archival
35193
- proposalId: uuid("proposal_id"),
35194
- sessionId: uuid("session_id"),
35195
- createdAt: timestamp("created_at").defaultNow().notNull()
35196
- },
35197
- (table) => ({
35198
- userIdIdx: index("credit_transactions_user_id_idx").on(table.userId),
35199
- createdAtIdx: index("credit_transactions_created_at_idx").on(table.createdAt)
35200
- })
35201
- );
35202
- }
35203
- });
35204
-
35205
35307
  // ../../packages/database/src/schema/_archived/index.ts
35206
35308
  var init_archived = __esm({
35207
35309
  "../../packages/database/src/schema/_archived/index.ts"() {
@@ -35210,7 +35312,6 @@ var init_archived = __esm({
35210
35312
  init_course_matrices();
35211
35313
  init_matrix_modules();
35212
35314
  init_course_variants();
35213
- init_user_credits();
35214
35315
  }
35215
35316
  });
35216
35317
 
@@ -36417,7 +36518,6 @@ var init_system_config = __esm({
36417
36518
  "business.limits.free_brainstorm_sessions": "business.limits.free_brainstorm_sessions",
36418
36519
  // Feature Flags
36419
36520
  "feature.spark_chat_enabled": "feature.spark_chat_enabled",
36420
- "feature.ai_course_generation_enabled": "feature.ai_course_generation_enabled",
36421
36521
  "feature.analytics_enabled": "feature.analytics_enabled",
36422
36522
  "feature.mentorship_enabled": "feature.mentorship_enabled",
36423
36523
  "feature.community_enabled": "feature.community_enabled",
@@ -38896,7 +38996,6 @@ __export(schema_exports, {
38896
38996
  creditSubscriptionPlans: () => creditSubscriptionPlans,
38897
38997
  creditSubscriptionStatusEnum: () => creditSubscriptionStatusEnum,
38898
38998
  creditSubscriptions: () => creditSubscriptions,
38899
- creditTransactions: () => creditTransactions2,
38900
38999
  creditWalletTransactionTypeEnum: () => creditWalletTransactionTypeEnum,
38901
39000
  creditWalletTransactions: () => creditWalletTransactions,
38902
39001
  creditWallets: () => creditWallets,
@@ -39182,7 +39281,6 @@ __export(schema_exports, {
39182
39281
  testimonialStatusEnum: () => testimonialStatusEnum,
39183
39282
  threadStatusEnum: () => threadStatusEnum,
39184
39283
  toneEnum: () => toneEnum,
39185
- transactionTypeEnum: () => transactionTypeEnum,
39186
39284
  transactions: () => transactions2,
39187
39285
  transactionsRelations: () => transactionsRelations2,
39188
39286
  transferStatusEnum: () => transferStatusEnum,
@@ -39199,7 +39297,6 @@ __export(schema_exports, {
39199
39297
  userBadgesRelations: () => userBadgesRelations2,
39200
39298
  userChallengeScores: () => userChallengeScores,
39201
39299
  userChallengeScoresRelations: () => userChallengeScoresRelations,
39202
- userCredits: () => userCredits2,
39203
39300
  userFollows: () => userFollows2,
39204
39301
  userFollowsRelations: () => userFollowsRelations2,
39205
39302
  userPathEnrollments: () => userPathEnrollments,
@@ -40896,65 +40993,6 @@ var init_lessons2 = __esm({
40896
40993
  }
40897
40994
  });
40898
40995
 
40899
- // src/workspace/resolve.ts
40900
- import fs6 from "node:fs/promises";
40901
- import path5 from "node:path";
40902
- import os6 from "node:os";
40903
- function courseSlug(title) {
40904
- return title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
40905
- }
40906
- async function resolveWorkspace(courseTitle, basePath = DEFAULT_BASE) {
40907
- const slug = courseSlug(courseTitle);
40908
- const candidate = path5.join(basePath, slug);
40909
- try {
40910
- await fs6.access(path5.join(candidate, ".ana-config.json"));
40911
- return { found: true, workspacePath: candidate };
40912
- } catch {
40913
- return { found: false, workspacePath: null };
40914
- }
40915
- }
40916
- var DEFAULT_BASE;
40917
- var init_resolve = __esm({
40918
- "src/workspace/resolve.ts"() {
40919
- "use strict";
40920
- DEFAULT_BASE = path5.join(os6.homedir(), "study");
40921
- }
40922
- });
40923
-
40924
- // src/onboarding/status.ts
40925
- import fs7 from "node:fs/promises";
40926
- import path6 from "node:path";
40927
- async function resolveStoredWorkspace(workspacePath) {
40928
- if (!workspacePath) return null;
40929
- try {
40930
- await fs7.access(path6.join(workspacePath, ".ana-config.json"));
40931
- return workspacePath;
40932
- } catch {
40933
- return null;
40934
- }
40935
- }
40936
- async function getCourseOnboardingStatus(activeCourse, configDir) {
40937
- const onboardingState = await getCourseOnboardingState(activeCourse.courseId, configDir);
40938
- const initReady = Boolean(onboardingState?.initCompletedAt);
40939
- let workspacePath = await resolveStoredWorkspace(onboardingState?.workspacePath);
40940
- if (!workspacePath) {
40941
- const resolvedWorkspace = await resolveWorkspace(activeCourse.courseTitle);
40942
- workspacePath = resolvedWorkspace.workspacePath;
40943
- }
40944
- return {
40945
- initReady,
40946
- workspaceReady: Boolean(workspacePath),
40947
- workspacePath
40948
- };
40949
- }
40950
- var init_status = __esm({
40951
- "src/onboarding/status.ts"() {
40952
- "use strict";
40953
- init_session();
40954
- init_resolve();
40955
- }
40956
- });
40957
-
40958
40996
  // src/commands/start.ts
40959
40997
  import { Command as Command8 } from "commander";
40960
40998
  async function runStart(opts, deps = defaultDeps2) {
@@ -41190,10 +41228,14 @@ var init_lesson = __esm({
41190
41228
  output(formatLessonContent(content), { json: false });
41191
41229
  }
41192
41230
  if (content.type === "exercise") {
41193
- const ws = await resolveWorkspace(activeCourse.courseTitle);
41231
+ const onboardingState = await getCourseOnboardingState(activeCourse.courseId);
41232
+ const ws = await resolveEffectiveWorkspace(
41233
+ activeCourse.courseTitle,
41234
+ onboardingState?.workspacePath
41235
+ );
41194
41236
  if (!ws.found) {
41195
41237
  process.stderr.write(
41196
- "\n\u{1F4A1} Dica: rode `tostudy workspace setup` para criar um workspace local para exerc\xEDcios.\n"
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"
41197
41239
  );
41198
41240
  }
41199
41241
  }
@@ -41270,8 +41312,8 @@ var init_exercises = __esm({
41270
41312
  });
41271
41313
 
41272
41314
  // src/commands/validate.ts
41273
- import fs8 from "node:fs";
41274
- import path7 from "node:path";
41315
+ import fs7 from "node:fs";
41316
+ import path6 from "node:path";
41275
41317
  import { Command as Command13 } from "commander";
41276
41318
  var logger12, validateCommand;
41277
41319
  var init_validate = __esm({
@@ -41296,17 +41338,17 @@ var init_validate = __esm({
41296
41338
  }
41297
41339
  let solution;
41298
41340
  if (opts.stdin) {
41299
- solution = fs8.readFileSync("/dev/stdin", "utf-8");
41341
+ solution = fs7.readFileSync("/dev/stdin", "utf-8");
41300
41342
  } else if (file2) {
41301
- if (!fs8.existsSync(file2)) {
41343
+ if (!fs7.existsSync(file2)) {
41302
41344
  error(`Arquivo n\xE3o encontrado: ${file2}`);
41303
41345
  }
41304
- solution = fs8.readFileSync(file2, "utf-8");
41346
+ solution = fs7.readFileSync(file2, "utf-8");
41305
41347
  } else {
41306
41348
  error("Forne\xE7a um arquivo ou use --stdin.\n\nExemplo: tostudy validate resposta.md");
41307
41349
  }
41308
41350
  if (file2 && activeCourse.courseTags?.length) {
41309
- const ext = path7.extname(file2).toLowerCase();
41351
+ const ext = path6.extname(file2).toLowerCase();
41310
41352
  const LANG_EXTENSIONS = {
41311
41353
  ".html": ["html", "html5"],
41312
41354
  ".css": ["css"],
@@ -41739,14 +41781,14 @@ var init_init = __esm({
41739
41781
  });
41740
41782
 
41741
41783
  // ../../packages/tostudy-core/src/workspace/setup-workspace.ts
41742
- import fs9 from "node:fs/promises";
41743
- import path8 from "node:path";
41784
+ import fs8 from "node:fs/promises";
41785
+ import path7 from "node:path";
41744
41786
  async function setupWorkspace(input2) {
41745
- const workspacePath = path8.join(input2.basePath, input2.courseSlug);
41787
+ const workspacePath = path7.join(input2.basePath, input2.courseSlug);
41746
41788
  for (const dir of WORKSPACE_DIRS) {
41747
- await fs9.mkdir(path8.join(workspacePath, dir), { recursive: true });
41789
+ await fs8.mkdir(path7.join(workspacePath, dir), { recursive: true });
41748
41790
  }
41749
- const configPath = path8.join(workspacePath, ".ana-config.json");
41791
+ const configPath = path7.join(workspacePath, ".ana-config.json");
41750
41792
  const config2 = {
41751
41793
  courseId: input2.courseId,
41752
41794
  courseSlug: input2.courseSlug,
@@ -41756,7 +41798,7 @@ async function setupWorkspace(input2) {
41756
41798
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
41757
41799
  lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
41758
41800
  };
41759
- await fs9.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
41801
+ await fs8.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
41760
41802
  const readme = [
41761
41803
  `# ${input2.courseName}`,
41762
41804
  "",
@@ -41781,7 +41823,7 @@ async function setupWorkspace(input2) {
41781
41823
  "tostudy vault sync # Sincronizar progresso",
41782
41824
  "```"
41783
41825
  ].join("\n");
41784
- await fs9.writeFile(path8.join(workspacePath, "README.md"), readme, "utf-8");
41826
+ await fs8.writeFile(path7.join(workspacePath, "README.md"), readme, "utf-8");
41785
41827
  return { workspacePath, directories: WORKSPACE_DIRS, configPath };
41786
41828
  }
41787
41829
  var WORKSPACE_DIRS;
@@ -41882,8 +41924,8 @@ var init_templates = __esm({
41882
41924
  });
41883
41925
 
41884
41926
  // ../../packages/tostudy-core/src/workspace/extract-exercise.ts
41885
- import fs10 from "node:fs/promises";
41886
- import path9 from "node:path";
41927
+ import fs9 from "node:fs/promises";
41928
+ import path8 from "node:path";
41887
41929
  function padOrder(n) {
41888
41930
  return String(n).padStart(2, "0");
41889
41931
  }
@@ -41907,16 +41949,16 @@ async function extractExercise(input2) {
41907
41949
  const { lessonData, exerciseTier, workspacePath } = input2;
41908
41950
  const moduleDir = `${padOrder(lessonData.moduleOrder)}-${lessonData.moduleSlug}`;
41909
41951
  const lessonDir = `${padOrder(lessonData.lessonOrder)}-${lessonData.lessonSlug}`;
41910
- const exercisePath = path9.join(workspacePath, "exercises", moduleDir, lessonDir);
41911
- await fs10.mkdir(exercisePath, { recursive: true });
41952
+ const exercisePath = path8.join(workspacePath, "exercises", moduleDir, lessonDir);
41953
+ await fs9.mkdir(exercisePath, { recursive: true });
41912
41954
  const extractedFiles = [];
41913
41955
  let hasStarterCode = false;
41914
41956
  if (lessonData.sandpackConfig?.files) {
41915
41957
  for (const [filePath, fileData] of Object.entries(lessonData.sandpackConfig.files)) {
41916
41958
  const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
41917
- const fullPath = path9.join(exercisePath, cleanPath);
41918
- await fs10.mkdir(path9.dirname(fullPath), { recursive: true });
41919
- await fs10.writeFile(fullPath, fileData.code, "utf-8");
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");
41920
41962
  extractedFiles.push(cleanPath);
41921
41963
  hasStarterCode = true;
41922
41964
  }
@@ -41924,13 +41966,13 @@ async function extractExercise(input2) {
41924
41966
  const tierData = getTierData(lessonData.structuredData, exerciseTier);
41925
41967
  const tierCode = tierData?.code;
41926
41968
  if (tierCode) {
41927
- await fs10.writeFile(path9.join(exercisePath, "exercise.js"), tierCode, "utf-8");
41969
+ await fs9.writeFile(path8.join(exercisePath, "exercise.js"), tierCode, "utf-8");
41928
41970
  extractedFiles.push("exercise.js");
41929
41971
  hasStarterCode = true;
41930
41972
  } else {
41931
41973
  const starter = getStarterCode(lessonData.structuredData);
41932
41974
  if (starter) {
41933
- await fs10.writeFile(path9.join(exercisePath, "exercise.js"), starter, "utf-8");
41975
+ await fs9.writeFile(path8.join(exercisePath, "exercise.js"), starter, "utf-8");
41934
41976
  extractedFiles.push("exercise.js");
41935
41977
  hasStarterCode = true;
41936
41978
  }
@@ -41948,8 +41990,8 @@ async function extractExercise(input2) {
41948
41990
  ...exerciseDeps
41949
41991
  }
41950
41992
  };
41951
- await fs10.writeFile(
41952
- path9.join(exercisePath, "package.json"),
41993
+ await fs9.writeFile(
41994
+ path8.join(exercisePath, "package.json"),
41953
41995
  JSON.stringify(pkgJson, null, 2),
41954
41996
  "utf-8"
41955
41997
  );
@@ -41961,20 +42003,20 @@ async function extractExercise(input2) {
41961
42003
  );
41962
42004
  for (const [configFile, configContent] of Object.entries(scaffold.configs)) {
41963
42005
  if (!sandpackFileNames.has(configFile)) {
41964
- await fs10.writeFile(path9.join(exercisePath, configFile), configContent, "utf-8");
42006
+ await fs9.writeFile(path8.join(exercisePath, configFile), configContent, "utf-8");
41965
42007
  extractedFiles.push(configFile);
41966
42008
  }
41967
42009
  }
41968
42010
  const setupSh = `#!/bin/sh
41969
42011
  ${scaffold.setupScript}
41970
42012
  `;
41971
- await fs10.writeFile(path9.join(exercisePath, "setup.sh"), setupSh, "utf-8");
42013
+ await fs9.writeFile(path8.join(exercisePath, "setup.sh"), setupSh, "utf-8");
41972
42014
  extractedFiles.push("setup.sh");
41973
42015
  }
41974
42016
  }
41975
42017
  const readme = generateReadme(lessonData, exerciseTier);
41976
- const readmePath = path9.join(exercisePath, "README.md");
41977
- await fs10.writeFile(readmePath, readme, "utf-8");
42018
+ const readmePath = path8.join(exercisePath, "README.md");
42019
+ await fs9.writeFile(readmePath, readme, "utf-8");
41978
42020
  extractedFiles.push("README.md");
41979
42021
  return {
41980
42022
  exercisePath,
@@ -42046,9 +42088,9 @@ var init_workspace = __esm({
42046
42088
 
42047
42089
  // src/commands/workspace.ts
42048
42090
  import { Command as Command16 } from "commander";
42049
- import path10 from "node:path";
42050
- import os7 from "node:os";
42051
- import fs11 from "node:fs/promises";
42091
+ import path9 from "node:path";
42092
+ import os9 from "node:os";
42093
+ import fs10 from "node:fs/promises";
42052
42094
  var logger14, workspaceCommand;
42053
42095
  var init_workspace2 = __esm({
42054
42096
  "src/commands/workspace.ts"() {
@@ -42056,31 +42098,67 @@ var init_workspace2 = __esm({
42056
42098
  init_src();
42057
42099
  init_workspace();
42058
42100
  init_session();
42101
+ init_resolve();
42059
42102
  logger14 = createLogger("cli:workspace");
42060
42103
  workspaceCommand = new Command16("workspace").description(
42061
42104
  "Gerenciar workspace de estudo local"
42062
42105
  );
42063
- workspaceCommand.command("setup").description("Criar estrutura do workspace para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path10.join(os7.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
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) => {
42064
42107
  try {
42065
42108
  await requireSession();
42066
42109
  const activeCourse = await requireActiveCourse();
42067
- const result = await setupWorkspace({
42068
- courseId: activeCourse.courseId,
42069
- courseSlug: activeCourse.courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60),
42070
- courseName: activeCourse.courseTitle,
42071
- basePath: opts.path,
42072
- locale: "pt-BR"
42073
- });
42074
- await setCourseWorkspacePath(activeCourse.courseId, result.workspacePath);
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 };
42075
42153
  if (opts.json) {
42076
42154
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
42077
42155
  } else {
42078
42156
  process.stdout.write(
42079
42157
  `
42080
- \u2705 Workspace criado em: ${result.workspacePath}
42158
+ \u2705 Workspace criado em: ${workspacePath}
42081
42159
 
42082
42160
  Diret\xF3rios:
42083
- ${result.directories.map((d) => ` \u{1F4C1} ${d}/`).join("\n")}
42161
+ ${directories.map((d) => ` \u{1F4C1} ${d}/`).join("\n")}
42084
42162
 
42085
42163
  Pr\xF3ximo passo: tostudy export
42086
42164
  `
@@ -42093,62 +42171,68 @@ Pr\xF3ximo passo: tostudy export
42093
42171
  process.exit(1);
42094
42172
  }
42095
42173
  });
42096
- workspaceCommand.command("status").description("Mostrar status do workspace do curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path10.join(os7.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
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) => {
42097
42175
  try {
42098
42176
  const activeCourse = await requireActiveCourse();
42099
- const courseSlug2 = activeCourse.courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
42100
- const workspacePath = path10.join(opts.path, courseSlug2);
42101
- let configData = null;
42102
- try {
42103
- const raw = await fs11.readFile(path10.join(workspacePath, ".ana-config.json"), "utf-8");
42104
- configData = JSON.parse(raw);
42105
- } catch {
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) {
42106
42185
  process.stderr.write(
42107
42186
  "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
42108
42187
  );
42109
42188
  process.exit(1);
42110
42189
  }
42111
- const exercisesDir = path10.join(workspacePath, "exercises");
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");
42112
42199
  let exerciseCount = 0;
42113
42200
  try {
42114
- const moduleDirs = await fs11.readdir(exercisesDir);
42201
+ const moduleDirs = await fs10.readdir(exercisesDir);
42115
42202
  for (const modDir of moduleDirs) {
42116
- const modPath = path10.join(exercisesDir, modDir);
42117
- const stat = await fs11.stat(modPath);
42203
+ const modPath = path9.join(exercisesDir, modDir);
42204
+ const stat = await fs10.stat(modPath);
42118
42205
  if (stat.isDirectory()) {
42119
- const lessonDirs = await fs11.readdir(modPath);
42206
+ const lessonDirs = await fs10.readdir(modPath);
42120
42207
  for (const lessonDir of lessonDirs) {
42121
- const lessonPath = path10.join(modPath, lessonDir);
42122
- const lstat = await fs11.stat(lessonPath);
42208
+ const lessonPath = path9.join(modPath, lessonDir);
42209
+ const lstat = await fs10.stat(lessonPath);
42123
42210
  if (lstat.isDirectory()) exerciseCount++;
42124
42211
  }
42125
42212
  }
42126
42213
  }
42127
42214
  } catch {
42128
42215
  }
42129
- const generatedDir = path10.join(workspacePath, "generated");
42216
+ const generatedDir = path9.join(workspacePath, "generated");
42130
42217
  let artifactCount = 0;
42131
42218
  try {
42132
- const files = await fs11.readdir(generatedDir);
42219
+ const files = await fs10.readdir(generatedDir);
42133
42220
  artifactCount = files.length;
42134
42221
  } catch {
42135
42222
  }
42136
- const diagramsDir = path10.join(workspacePath, "diagrams");
42223
+ const diagramsDir = path9.join(workspacePath, "diagrams");
42137
42224
  let diagramCount = 0;
42138
42225
  try {
42139
- const files = await fs11.readdir(diagramsDir);
42226
+ const files = await fs10.readdir(diagramsDir);
42140
42227
  diagramCount = files.length;
42141
42228
  } catch {
42142
42229
  }
42143
- const vaultDir = path10.join(workspacePath, "vault");
42144
- let hasVault = false;
42145
- try {
42146
- await fs11.access(path10.join(vaultDir, ".ana-vault.json"));
42147
- hasVault = true;
42148
- } catch {
42149
- }
42230
+ const slug = courseSlug(activeCourse.courseTitle);
42231
+ const foundVaultPath = await findExistingVault(workspacePath, slug);
42232
+ const hasVault = foundVaultPath !== null;
42150
42233
  const status = {
42151
42234
  workspacePath,
42235
+ workspaceSource: ws.source,
42152
42236
  course: activeCourse.courseTitle,
42153
42237
  courseId: activeCourse.courseId,
42154
42238
  exercisesExtracted: exerciseCount,
@@ -42160,11 +42244,12 @@ Pr\xF3ximo passo: tostudy export
42160
42244
  if (opts.json) {
42161
42245
  process.stdout.write(JSON.stringify(status, null, 2) + "\n");
42162
42246
  } else {
42247
+ const sourceLabel = ws.source === "cwd" ? " (pasta atual)" : ws.source === "stored" ? " (configurado)" : ws.source === "default" ? " (~/study/)" : "";
42163
42248
  process.stdout.write(
42164
42249
  [
42165
42250
  "",
42166
42251
  `\u{1F4DA} **${activeCourse.courseTitle}**`,
42167
- `\u{1F4C1} ${workspacePath}`,
42252
+ `\u{1F4C1} ${workspacePath}${sourceLabel}`,
42168
42253
  "",
42169
42254
  ` Exerc\xEDcios extra\xEDdos: ${exerciseCount}`,
42170
42255
  ` Artefatos exportados: ${artifactCount}`,
@@ -42187,8 +42272,9 @@ Pr\xF3ximo passo: tostudy export
42187
42272
 
42188
42273
  // src/commands/export.ts
42189
42274
  import { Command as Command17 } from "commander";
42190
- import path11 from "node:path";
42191
- import os8 from "node:os";
42275
+ import path10 from "node:path";
42276
+ import os10 from "node:os";
42277
+ import fs11 from "node:fs/promises";
42192
42278
  var logger15, exportCommand;
42193
42279
  var init_export = __esm({
42194
42280
  "src/commands/export.ts"() {
@@ -42199,7 +42285,7 @@ var init_export = __esm({
42199
42285
  init_session();
42200
42286
  init_resolve();
42201
42287
  logger15 = createLogger("cli:export");
42202
- exportCommand = new Command17("export").description("Extrair exerc\xEDcio atual para o workspace local").option("--tier <tier>", "Tier do exerc\xEDcio: guided, semiGuided, challenging", "guided").option("--path <dir>", "Diret\xF3rio base do workspace", path11.join(os8.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
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) => {
42203
42289
  try {
42204
42290
  const session = await requireSession();
42205
42291
  const activeCourse = await requireActiveCourse();
@@ -42209,10 +42295,57 @@ var init_export = __esm({
42209
42295
  process.stderr.write("\u274C Nenhuma li\xE7\xE3o ativa. Execute 'tostudy start' primeiro.\n");
42210
42296
  process.exit(1);
42211
42297
  }
42212
- const ws = await resolveWorkspace(activeCourse.courseTitle, opts.path);
42213
- if (!ws.found) {
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) {
42214
42347
  process.stderr.write(
42215
- "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
42348
+ "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' ou rode 'tostudy select' desta pasta.\n"
42216
42349
  );
42217
42350
  process.exit(1);
42218
42351
  }
@@ -42255,45 +42388,41 @@ ${result.files.map((f) => ` \u{1F4C4} ${f}`).join("\n")}
42255
42388
  // src/commands/open.ts
42256
42389
  import { Command as Command18 } from "commander";
42257
42390
  import { execFile as execFile3 } from "node:child_process";
42258
- import fs12 from "node:fs/promises";
42259
- import path12 from "node:path";
42260
- import os9 from "node:os";
42261
- async function findWorkspacePath(courseTitle, basePath) {
42262
- const slug = courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
42263
- const candidate = path12.join(basePath, slug);
42264
- try {
42265
- await fs12.access(path12.join(candidate, ".ana-config.json"));
42266
- return candidate;
42267
- } catch {
42268
- return null;
42269
- }
42270
- }
42391
+ import path11 from "node:path";
42392
+ import os11 from "node:os";
42271
42393
  var logger16, openCommand;
42272
42394
  var init_open = __esm({
42273
42395
  "src/commands/open.ts"() {
42274
42396
  "use strict";
42275
42397
  init_src();
42276
42398
  init_session();
42399
+ init_resolve();
42277
42400
  logger16 = createLogger("cli:open");
42278
- openCommand = new Command18("open").description("Abrir workspace do curso na IDE").option("--path <dir>", "Diret\xF3rio base do workspace", path12.join(os9.homedir(), "study")).action(async (opts) => {
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) => {
42279
42402
  try {
42280
42403
  const activeCourse = await requireActiveCourse();
42281
- const workspacePath = await findWorkspacePath(activeCourse.courseTitle, opts.path);
42282
- if (!workspacePath) {
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) {
42283
42412
  process.stderr.write(
42284
42413
  "\u274C Workspace n\xE3o encontrado. Execute 'tostudy workspace setup' primeiro.\n"
42285
42414
  );
42286
42415
  process.exit(1);
42287
42416
  }
42288
42417
  const editor = process.env["EDITOR"] ?? "code";
42289
- execFile3(editor, [workspacePath], (err) => {
42418
+ execFile3(editor, [ws.workspacePath], (err) => {
42290
42419
  if (err) {
42291
- logger16.error("open failed", { editor, workspacePath });
42420
+ logger16.error("open failed", { editor, workspacePath: ws.workspacePath });
42292
42421
  process.stderr.write(`\u274C Falha ao abrir: ${err.message}
42293
42422
  `);
42294
42423
  process.exit(1);
42295
42424
  }
42296
- process.stdout.write(`\u2705 Aberto em ${editor}: ${workspacePath}
42425
+ process.stdout.write(`\u2705 Aberto em ${editor}: ${ws.workspacePath}
42297
42426
  `);
42298
42427
  });
42299
42428
  } catch (err) {
@@ -42324,24 +42453,24 @@ var init_types3 = __esm({
42324
42453
  });
42325
42454
 
42326
42455
  // ../../packages/tostudy-core/src/vault/write-vault.ts
42327
- import fs13 from "node:fs/promises";
42328
- import path13 from "node:path";
42456
+ import fs12 from "node:fs/promises";
42457
+ import path12 from "node:path";
42329
42458
  async function writeVaultFiles(files, outputPath, courseId, courseSlug2) {
42330
42459
  for (const file2 of files) {
42331
- const fullPath = path13.join(outputPath, file2.relativePath);
42332
- await fs13.mkdir(path13.dirname(fullPath), { recursive: true });
42333
- await fs13.writeFile(fullPath, file2.content, "utf-8");
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");
42334
42463
  }
42335
- const vaultPath = path13.join(outputPath, courseSlug2);
42464
+ const vaultPath = path12.join(outputPath, courseSlug2);
42336
42465
  const marker = {
42337
42466
  courseId,
42338
42467
  courseSlug: courseSlug2,
42339
42468
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
42340
42469
  version: VAULT_MARKER_VERSION
42341
42470
  };
42342
- await fs13.mkdir(vaultPath, { recursive: true });
42343
- await fs13.writeFile(
42344
- path13.join(vaultPath, VAULT_MARKER_FILENAME),
42471
+ await fs12.mkdir(vaultPath, { recursive: true });
42472
+ await fs12.writeFile(
42473
+ path12.join(vaultPath, VAULT_MARKER_FILENAME),
42345
42474
  JSON.stringify(marker, null, 2),
42346
42475
  "utf-8"
42347
42476
  );
@@ -42366,9 +42495,9 @@ var init_vault = __esm({
42366
42495
 
42367
42496
  // src/commands/vault.ts
42368
42497
  import { Command as Command19 } from "commander";
42369
- import path14 from "node:path";
42370
- import os10 from "node:os";
42371
- import fs14 from "node:fs/promises";
42498
+ import path13 from "node:path";
42499
+ import os12 from "node:os";
42500
+ import fs13 from "node:fs/promises";
42372
42501
  var logger17, vaultCommand;
42373
42502
  var init_vault2 = __esm({
42374
42503
  "src/commands/vault.ts"() {
@@ -42378,17 +42507,31 @@ var init_vault2 = __esm({
42378
42507
  init_courses();
42379
42508
  init_http2();
42380
42509
  init_session();
42510
+ init_resolve();
42381
42511
  logger17 = createLogger("cli:vault");
42382
42512
  vaultCommand = new Command19("vault").description("Gerenciar vault Obsidian do curso");
42383
- vaultCommand.command("init").description("Gerar vault Obsidian para o curso ativo").option("--path <dir>", "Diret\xF3rio base do workspace", path14.join(os10.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
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) => {
42384
42514
  try {
42385
42515
  const session = await requireSession();
42386
42516
  const activeCourse = await requireActiveCourse();
42387
42517
  const driftWarning = await checkCourseDrift();
42388
42518
  if (driftWarning) process.stderr.write(driftWarning + "\n");
42389
- const courseSlug2 = activeCourse.courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
42390
- const workspacePath = path14.join(opts.path, courseSlug2);
42391
- const vaultOutputPath = path14.join(workspacePath, "vault");
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);
42392
42535
  const res = await fetch(`${session.apiUrl}/api/cli/vault/init`, {
42393
42536
  method: "POST",
42394
42537
  headers: {
@@ -42410,7 +42553,7 @@ var init_vault2 = __esm({
42410
42553
  data.files,
42411
42554
  vaultOutputPath,
42412
42555
  activeCourse.courseId,
42413
- courseSlug2
42556
+ slug
42414
42557
  );
42415
42558
  logger17.info("Vault generated", {
42416
42559
  courseId: activeCourse.courseId,
@@ -42451,25 +42594,36 @@ Para visualizar:
42451
42594
  process.exit(1);
42452
42595
  }
42453
42596
  });
42454
- vaultCommand.command("sync").description("Sincronizar progresso do curso com o vault local").option("--path <dir>", "Diret\xF3rio base do workspace", path14.join(os10.homedir(), "study")).option("--json", "Output structured JSON").action(async (opts) => {
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) => {
42455
42598
  try {
42456
42599
  const session = await requireSession();
42457
42600
  const activeCourse = await requireActiveCourse();
42458
42601
  const driftWarning = await checkCourseDrift();
42459
42602
  if (driftWarning) process.stderr.write(driftWarning + "\n");
42460
- const courseSlug2 = activeCourse.courseTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
42461
- const vaultPath = path14.join(opts.path, courseSlug2, "vault");
42462
- try {
42463
- await fs14.access(path14.join(vaultPath, ".ana-vault.json"));
42464
- } catch {
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) {
42465
42619
  process.stderr.write("\u274C Vault n\xE3o encontrado. Execute 'tostudy vault init' primeiro.\n");
42466
42620
  process.exit(1);
42467
42621
  }
42468
42622
  const data = createHttpProvider(session.apiUrl, session.token);
42469
42623
  const deps = { data, logger: logger17 };
42470
42624
  const progress3 = await getProgress({ enrollmentId: activeCourse.enrollmentId }, deps);
42471
- const markerPath = path14.join(vaultPath, ".ana-vault.json");
42472
- const markerRaw = await fs14.readFile(markerPath, "utf-8");
42625
+ const markerPath = path13.join(vaultPath, ".ana-vault.json");
42626
+ const markerRaw = await fs13.readFile(markerPath, "utf-8");
42473
42627
  const marker = JSON.parse(markerRaw);
42474
42628
  marker.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
42475
42629
  marker.progress = {
@@ -42477,10 +42631,10 @@ Para visualizar:
42477
42631
  currentModule: progress3.currentModule.title,
42478
42632
  currentLesson: progress3.currentLesson.title
42479
42633
  };
42480
- await fs14.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
42481
- const courseIndexPath = path14.join(vaultPath, courseSlug2, "index.md");
42634
+ await fs13.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
42635
+ const courseIndexPath = path13.join(vaultPath, slug, "index.md");
42482
42636
  try {
42483
- let indexContent = await fs14.readFile(courseIndexPath, "utf-8");
42637
+ let indexContent = await fs13.readFile(courseIndexPath, "utf-8");
42484
42638
  indexContent = indexContent.replace(/\n---\n\n> 📊 Progresso:.*\n/g, "");
42485
42639
  const titleEnd = indexContent.indexOf("\n");
42486
42640
  if (titleEnd !== -1) {
@@ -42491,7 +42645,7 @@ Para visualizar:
42491
42645
  `;
42492
42646
  indexContent = indexContent.slice(0, titleEnd) + banner + indexContent.slice(titleEnd);
42493
42647
  }
42494
- await fs14.writeFile(courseIndexPath, indexContent, "utf-8");
42648
+ await fs13.writeFile(courseIndexPath, indexContent, "utf-8");
42495
42649
  } catch {
42496
42650
  }
42497
42651
  const syncedAt = marker.lastSyncedAt;
@@ -42596,15 +42750,85 @@ var init_profile = __esm({
42596
42750
  }
42597
42751
  });
42598
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
+
42599
42823
  // src/cli.ts
42600
42824
  var cli_exports = {};
42601
42825
  __export(cli_exports, {
42602
42826
  CLI_VERSION: () => CLI_VERSION,
42603
42827
  createProgram: () => createProgram
42604
42828
  });
42605
- import { Command as Command21 } from "commander";
42829
+ import { Command as Command22 } from "commander";
42606
42830
  function createProgram() {
42607
- const program2 = new Command21();
42831
+ const program2 = new Command22();
42608
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");
42609
42833
  program2.addCommand(setupCommand);
42610
42834
  program2.addCommand(doctorCommand);
@@ -42623,6 +42847,7 @@ function createProgram() {
42623
42847
  program2.addCommand(menuCommand);
42624
42848
  program2.addCommand(profileCommand);
42625
42849
  program2.addCommand(workspaceCommand);
42850
+ program2.addCommand(syncCommand);
42626
42851
  program2.addCommand(exportCommand);
42627
42852
  program2.addCommand(openCommand);
42628
42853
  program2.addCommand(vaultCommand);
@@ -42652,7 +42877,8 @@ var init_cli = __esm({
42652
42877
  init_open();
42653
42878
  init_vault2();
42654
42879
  init_profile();
42655
- CLI_VERSION = true ? "0.7.4" : "0.7.1";
42880
+ init_sync();
42881
+ CLI_VERSION = true ? "0.8.0" : "0.7.1";
42656
42882
  }
42657
42883
  });
42658
42884