minerva-cli 0.1.0 → 0.2.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/index.cjs CHANGED
@@ -48,7 +48,7 @@ var import_commander20 = require("commander");
48
48
  // src/config/constants.ts
49
49
  var APP_NAME = "minerva";
50
50
  var APP_DESCRIPTION = "Organiza\xE7\xE3o acad\xEAmica para estudantes de ADS da FATEC \u{1F989}";
51
- var APP_VERSION = "0.1.0";
51
+ var APP_VERSION = "0.2.0";
52
52
  var DEFAULT_VAULT_DIRNAME = "fatec-ads";
53
53
  var MATRIZ_URL = "https://fatectq.cps.sp.gov.br/matriz-curricular-ads/";
54
54
 
@@ -610,14 +610,20 @@ function disciplinaDir(root, semestre, disciplina) {
610
610
  async function scaffoldDisciplina(root, semestre, disciplina) {
611
611
  const dir = disciplinaDir(root, semestre, disciplina.nome);
612
612
  await Promise.all(
613
- DISCIPLINA_FOLDERS.map((folder) => import_fs_extra5.default.ensureDir(import_node_path6.default.join(dir, folder)))
613
+ DISCIPLINA_FOLDERS.map(
614
+ (folder) => import_fs_extra5.default.ensureDir(import_node_path6.default.join(dir, folder))
615
+ )
614
616
  );
615
617
  const files = [
616
618
  [
617
619
  "README.md",
618
620
  () => renderTemplate(
619
621
  "disciplina",
620
- { disciplina: disciplina.nome, semestre, aulas: disciplina.aulas },
622
+ {
623
+ disciplina: disciplina.nome,
624
+ semestre,
625
+ aulas: disciplina.aulas
626
+ },
621
627
  root
622
628
  )
623
629
  ],
@@ -870,7 +876,9 @@ function doctorCommand() {
870
876
  {
871
877
  label: "Node.js",
872
878
  run: () => {
873
- const major = Number(process.versions.node.split(".")[0]);
879
+ const major = Number(
880
+ process.versions.node.split(".")[0]
881
+ );
874
882
  if (major < 20) {
875
883
  throw new Error(
876
884
  `v${process.versions.node} \u2014 atualize para a LTS (>= 20)`
@@ -887,7 +895,9 @@ function doctorCommand() {
887
895
  label: "Vault Minerva",
888
896
  run: () => {
889
897
  if (!root) {
890
- throw new Error('n\xE3o encontrado \u2014 rode "minerva init"');
898
+ throw new Error(
899
+ 'n\xE3o encontrado \u2014 rode "minerva init"'
900
+ );
891
901
  }
892
902
  return Promise.resolve(root);
893
903
  }
@@ -917,7 +927,9 @@ function doctorCommand() {
917
927
  label: "Reposit\xF3rio Git",
918
928
  run: async () => {
919
929
  if (!await import_fs_extra7.default.pathExists(import_node_path10.default.join(root, ".git"))) {
920
- throw new Error('ausente \u2014 rode "git init" no vault');
930
+ throw new Error(
931
+ 'ausente \u2014 rode "git init" no vault'
932
+ );
921
933
  }
922
934
  return "ok";
923
935
  }
@@ -925,7 +937,9 @@ function doctorCommand() {
925
937
  {
926
938
  label: "Vault do Obsidian",
927
939
  run: async () => {
928
- if (!await import_fs_extra7.default.pathExists(import_node_path10.default.join(root, ".obsidian"))) {
940
+ if (!await import_fs_extra7.default.pathExists(
941
+ import_node_path10.default.join(root, ".obsidian")
942
+ )) {
929
943
  throw new Error(
930
944
  'pasta .obsidian ausente \u2014 rode "minerva init" novamente'
931
945
  );
@@ -978,7 +992,9 @@ async function loadEstudoLog(root) {
978
992
  }
979
993
  async function registrarEstudo(vault, horas, disciplina) {
980
994
  if (!Number.isFinite(horas) || horas <= 0) {
981
- throw new Error("Informe um n\xFAmero de horas maior que zero (ex.: 1.5).");
995
+ throw new Error(
996
+ "Informe um n\xFAmero de horas maior que zero (ex.: 1.5)."
997
+ );
982
998
  }
983
999
  const sessao = {
984
1000
  data: todayISO(),
@@ -1024,12 +1040,16 @@ function estudoCommand() {
1024
1040
  );
1025
1041
  return;
1026
1042
  }
1027
- logger.title(`${vault.config.horasEstudadas}h estudadas no total`);
1043
+ logger.title(
1044
+ `${vault.config.horasEstudadas}h estudadas no total`
1045
+ );
1028
1046
  for (const [disciplina2, total] of totals) {
1029
1047
  logger.item(`${disciplina2}: ${total}h`);
1030
1048
  }
1031
1049
  if (totals.size === 0) {
1032
- logger.info("Registre sua primeira sess\xE3o: minerva estudo 1.5");
1050
+ logger.info(
1051
+ "Registre sua primeira sess\xE3o: minerva estudo 1.5"
1052
+ );
1033
1053
  }
1034
1054
  return;
1035
1055
  }
@@ -1078,7 +1098,9 @@ async function collectMarkdownFiles(dir) {
1078
1098
  const stack = [dir];
1079
1099
  while (stack.length > 0) {
1080
1100
  const current = stack.pop();
1081
- for (const entry of await import_fs_extra9.default.readdir(current, { withFileTypes: true })) {
1101
+ for (const entry of await import_fs_extra9.default.readdir(current, {
1102
+ withFileTypes: true
1103
+ })) {
1082
1104
  const full = import_node_path12.default.join(current, entry.name);
1083
1105
  if (entry.isDirectory()) stack.push(full);
1084
1106
  else if (entry.name.endsWith(".md")) files.push(full);
@@ -1091,14 +1113,22 @@ async function collectAllFlashcards(vault) {
1091
1113
  const stack = [vault.root];
1092
1114
  while (stack.length > 0) {
1093
1115
  const current = stack.pop();
1094
- for (const entry of await import_fs_extra9.default.readdir(current, { withFileTypes: true })) {
1095
- if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
1116
+ for (const entry of await import_fs_extra9.default.readdir(current, {
1117
+ withFileTypes: true
1118
+ })) {
1119
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
1120
+ continue;
1096
1121
  const full = import_node_path12.default.join(current, entry.name);
1097
1122
  if (!entry.isDirectory()) continue;
1098
1123
  if (entry.name.toLowerCase() === "flashcards") {
1099
1124
  const deck = import_node_path12.default.basename(import_node_path12.default.dirname(full));
1100
1125
  for (const file of await collectMarkdownFiles(full)) {
1101
- cards.push(...parseFlashcards(await import_fs_extra9.default.readFile(file, "utf8"), deck));
1126
+ cards.push(
1127
+ ...parseFlashcards(
1128
+ await import_fs_extra9.default.readFile(file, "utf8"),
1129
+ deck
1130
+ )
1131
+ );
1102
1132
  }
1103
1133
  } else {
1104
1134
  stack.push(full);
@@ -1173,22 +1203,26 @@ async function createNote(vault, input5) {
1173
1203
 
1174
1204
  // src/services/ai.ts
1175
1205
  var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
1176
- var AI_MODEL = "claude-opus-4-8";
1206
+ var ANTHROPIC_MODEL = "claude-opus-4-8";
1207
+ var OLLAMA_DEFAULT_MODEL = "llama3.2";
1208
+ var OLLAMA_DEFAULT_URL = "http://localhost:11434";
1209
+ var SYSTEM_PROMPT = "Voc\xEA \xE9 um monitor de estudos da FATEC (curso de An\xE1lise e Desenvolvimento de Sistemas). Responda sempre em portugu\xEAs do Brasil, em Markdown puro, sem pre\xE2mbulos.";
1177
1210
  function anthropicProvider() {
1178
1211
  if (!process.env["ANTHROPIC_API_KEY"]) {
1179
1212
  throw new Error(
1180
- 'Defina a vari\xE1vel de ambiente ANTHROPIC_API_KEY para usar os recursos de IA.\n Ex.: export ANTHROPIC_API_KEY="sk-ant-..."'
1213
+ 'Defina a vari\xE1vel de ambiente ANTHROPIC_API_KEY para usar o provedor Anthropic.\n Ex.: export ANTHROPIC_API_KEY="sk-ant-..."'
1181
1214
  );
1182
1215
  }
1183
1216
  const client = new import_sdk.default();
1184
1217
  return {
1218
+ name: `anthropic (${ANTHROPIC_MODEL})`,
1185
1219
  async generate(prompt, material) {
1186
1220
  try {
1187
1221
  const stream = client.messages.stream({
1188
- model: AI_MODEL,
1222
+ model: ANTHROPIC_MODEL,
1189
1223
  max_tokens: 64e3,
1190
1224
  thinking: { type: "adaptive" },
1191
- system: "Voc\xEA \xE9 um monitor de estudos da FATEC (curso de An\xE1lise e Desenvolvimento de Sistemas). Responda sempre em portugu\xEAs do Brasil, em Markdown puro, sem pre\xE2mbulos.",
1225
+ system: SYSTEM_PROMPT,
1192
1226
  messages: [
1193
1227
  {
1194
1228
  role: "user",
@@ -1204,7 +1238,9 @@ ${material}
1204
1238
  return message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
1205
1239
  } catch (error) {
1206
1240
  if (error instanceof import_sdk.default.AuthenticationError) {
1207
- throw new Error("ANTHROPIC_API_KEY inv\xE1lida. Verifique a chave.");
1241
+ throw new Error(
1242
+ "ANTHROPIC_API_KEY inv\xE1lida. Verifique a chave."
1243
+ );
1208
1244
  }
1209
1245
  if (error instanceof import_sdk.default.RateLimitError) {
1210
1246
  throw new Error(
@@ -1221,6 +1257,98 @@ ${material}
1221
1257
  }
1222
1258
  };
1223
1259
  }
1260
+ function ollamaProvider() {
1261
+ const baseUrl = (process.env["OLLAMA_URL"] ?? OLLAMA_DEFAULT_URL).replace(
1262
+ /\/$/,
1263
+ ""
1264
+ );
1265
+ const model = process.env["OLLAMA_MODEL"] ?? OLLAMA_DEFAULT_MODEL;
1266
+ const headers = {
1267
+ "content-type": "application/json"
1268
+ };
1269
+ if (process.env["OLLAMA_API_KEY"]) {
1270
+ headers["authorization"] = `Bearer ${process.env["OLLAMA_API_KEY"]}`;
1271
+ }
1272
+ return {
1273
+ name: `ollama (${model} @ ${baseUrl})`,
1274
+ async generate(prompt, material) {
1275
+ let response;
1276
+ try {
1277
+ response = await fetch(`${baseUrl}/api/chat`, {
1278
+ method: "POST",
1279
+ headers,
1280
+ body: JSON.stringify({
1281
+ model,
1282
+ stream: false,
1283
+ messages: [
1284
+ { role: "system", content: SYSTEM_PROMPT },
1285
+ {
1286
+ role: "user",
1287
+ content: `${prompt}
1288
+
1289
+ <material>
1290
+ ${material}
1291
+ </material>`
1292
+ }
1293
+ ]
1294
+ })
1295
+ });
1296
+ } catch (cause) {
1297
+ throw new Error(
1298
+ `N\xE3o consegui falar com o Ollama em ${baseUrl}.
1299
+ Local: instale com "curl -fsSL https://ollama.com/install.sh | sh" e rode "ollama serve".
1300
+ VPS: defina OLLAMA_URL apontando para o seu servidor.`,
1301
+ { cause }
1302
+ );
1303
+ }
1304
+ if (response.status === 404) {
1305
+ throw new Error(
1306
+ `O modelo "${model}" n\xE3o est\xE1 instalado no Ollama. Rode: ollama pull ${model}`
1307
+ );
1308
+ }
1309
+ if (!response.ok) {
1310
+ throw new Error(
1311
+ `Ollama respondeu HTTP ${response.status}: ${await response.text()}`
1312
+ );
1313
+ }
1314
+ const data = await response.json();
1315
+ const content = data.message?.content;
1316
+ if (!content) {
1317
+ throw new Error("O Ollama retornou uma resposta vazia.");
1318
+ }
1319
+ return content;
1320
+ }
1321
+ };
1322
+ }
1323
+ async function ollamaDisponivel() {
1324
+ const baseUrl = (process.env["OLLAMA_URL"] ?? OLLAMA_DEFAULT_URL).replace(
1325
+ /\/$/,
1326
+ ""
1327
+ );
1328
+ try {
1329
+ const response = await fetch(`${baseUrl}/api/tags`, {
1330
+ signal: AbortSignal.timeout(2e3)
1331
+ });
1332
+ return response.ok;
1333
+ } catch {
1334
+ return false;
1335
+ }
1336
+ }
1337
+ async function resolveProvider() {
1338
+ const forced = process.env["MINERVA_AI"]?.toLowerCase();
1339
+ if (forced === "anthropic") return anthropicProvider();
1340
+ if (forced === "ollama") return ollamaProvider();
1341
+ if (forced) {
1342
+ throw new Error(
1343
+ `Provedor desconhecido em MINERVA_AI: "${forced}". Use "anthropic" ou "ollama".`
1344
+ );
1345
+ }
1346
+ if (process.env["ANTHROPIC_API_KEY"]) return anthropicProvider();
1347
+ if (await ollamaDisponivel()) return ollamaProvider();
1348
+ throw new Error(
1349
+ 'Nenhum provedor de IA dispon\xEDvel. Escolha um:\n \u2022 Anthropic (pago): export ANTHROPIC_API_KEY="sk-ant-..."\n \u2022 Ollama (gr\xE1tis, local): instale em https://ollama.com e rode "ollama pull llama3.2"\n \u2022 Ollama (gr\xE1tis, na sua VPS): export OLLAMA_URL="http://SEU-IP:11434"'
1350
+ );
1351
+ }
1224
1352
  function resumoPrompt(disciplina, tema) {
1225
1353
  return `Gere um resumo de estudo sobre "${tema}" para a disciplina "${disciplina}", baseado nas anota\xE7\xF5es de aula fornecidas no material. Estruture com: vis\xE3o geral, pontos principais numerados, explica\xE7\xE3o com palavras simples e uma se\xE7\xE3o "Poss\xEDveis quest\xF5es de prova" com checkboxes (- [ ]). Se o material for insuficiente, complemente com seu conhecimento e sinalize o que n\xE3o veio das anota\xE7\xF5es.`;
1226
1354
  }
@@ -1245,7 +1373,9 @@ var DATE_HINT = "Use uma data v\xE1lida no formato YYYY-MM-DD.";
1245
1373
  async function readMaterial(dir) {
1246
1374
  if (!await import_fs_extra11.default.pathExists(dir)) return "";
1247
1375
  const parts = [];
1248
- for (const file of (await import_fs_extra11.default.readdir(dir)).filter((f) => f.endsWith(".md"))) {
1376
+ for (const file of (await import_fs_extra11.default.readdir(dir)).filter(
1377
+ (f) => f.endsWith(".md")
1378
+ )) {
1249
1379
  parts.push(await import_fs_extra11.default.readFile(import_node_path14.default.join(dir, file), "utf8"));
1250
1380
  }
1251
1381
  return parts.join("\n\n---\n\n");
@@ -1261,7 +1391,7 @@ function noteCommand(spec) {
1261
1391
  if (spec.ai) {
1262
1392
  command.option(
1263
1393
  "--ai",
1264
- `gera o conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/ (requer ANTHROPIC_API_KEY)`
1394
+ `gera o conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/ (Anthropic ou Ollama)`
1265
1395
  );
1266
1396
  }
1267
1397
  command.action(
@@ -1295,13 +1425,13 @@ function noteCommand(spec) {
1295
1425
  }
1296
1426
  let conteudoIA;
1297
1427
  if (spec.ai && options["ai"]) {
1298
- const provider = anthropicProvider();
1428
+ const provider = await resolveProvider();
1299
1429
  const sourceDir = import_node_path14.default.join(
1300
1430
  disciplinaDir(vault.root, semestre, disciplina),
1301
1431
  spec.ai.sourceFolder
1302
1432
  );
1303
1433
  const spinAi = spinner(
1304
- `Gerando conte\xFAdo com IA (${spec.ai.sourceFolder}/)...`
1434
+ `Gerando conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/...`
1305
1435
  ).start();
1306
1436
  try {
1307
1437
  const material = await readMaterial(sourceDir);
@@ -1325,11 +1455,14 @@ function noteCommand(spec) {
1325
1455
  extra
1326
1456
  });
1327
1457
  if (conteudoIA) {
1328
- await import_fs_extra11.default.appendFile(file, `
1458
+ await import_fs_extra11.default.appendFile(
1459
+ file,
1460
+ `
1329
1461
  ## Gerado por IA
1330
1462
 
1331
1463
  ${conteudoIA}
1332
- `);
1464
+ `
1465
+ );
1333
1466
  }
1334
1467
  spin.succeed(`Pronto! Nota criada em:`);
1335
1468
  logger.item(import_node_path14.default.relative(vault.root, file));
@@ -1423,14 +1556,18 @@ function flashcardsCommand() {
1423
1556
  );
1424
1557
  const create = flashcardsNewCommand();
1425
1558
  command.addCommand(create, { isDefault: true });
1426
- command.command("export").description("Exporta todos os flashcards para um TSV import\xE1vel no Anki").action(async () => {
1559
+ command.command("export").description(
1560
+ "Exporta todos os flashcards para um TSV import\xE1vel no Anki"
1561
+ ).action(async () => {
1427
1562
  const vault = await requireVault();
1428
1563
  const spin = spinner("Coletando flashcards do vault...").start();
1429
1564
  try {
1430
1565
  const { file, total } = await exportAnki(vault);
1431
1566
  spin.succeed(`${total} flashcard(s) exportado(s)!`);
1432
1567
  logger.item(import_node_path15.default.relative(vault.root, file));
1433
- logger.info("No Anki: Arquivo \u2192 Importar e selecione esse arquivo.");
1568
+ logger.info(
1569
+ "No Anki: Arquivo \u2192 Importar e selecione esse arquivo."
1570
+ );
1434
1571
  } catch (error) {
1435
1572
  spin.fail("Falha na exporta\xE7\xE3o.");
1436
1573
  throw error;
@@ -1481,7 +1618,10 @@ function initCommand() {
1481
1618
  return new import_commander11.Command("init").description("Cria a estrutura completa do curso (vault de estudos)").argument("[pasta]", "pasta de destino", DEFAULT_VAULT_DIRNAME).option("-s, --semestre <n>", "semestre atual (1-6)").option("-a, --aluno <nome>", "seu nome").action(
1482
1619
  async (pasta, options) => {
1483
1620
  logger.title("Minerva CLI \u2014 vamos montar sua gradua\xE7\xE3o!");
1484
- const aluno = options.aluno ?? await (0, import_prompts8.input)({ message: "Seu nome:", default: "Estudante" });
1621
+ const aluno = options.aluno ?? await (0, import_prompts8.input)({
1622
+ message: "Seu nome:",
1623
+ default: "Estudante"
1624
+ });
1485
1625
  const semestreAtual = options.semestre !== void 0 ? Number(options.semestre) : await (0, import_prompts8.number)({
1486
1626
  message: "Em qual semestre voc\xEA est\xE1? (1-6)",
1487
1627
  default: 1,
@@ -1506,7 +1646,10 @@ function initCommand() {
1506
1646
  spin.text = "Copiando templates...";
1507
1647
  await scaffoldTemplates(root);
1508
1648
  spin.text = "Criando semestres e disciplinas...";
1509
- const totalDisciplinas = await scaffoldSemestres(root, matrizOficial);
1649
+ const totalDisciplinas = await scaffoldSemestres(
1650
+ root,
1651
+ matrizOficial
1652
+ );
1510
1653
  spin.text = "Preparando vault do Obsidian...";
1511
1654
  await scaffoldObsidian(root);
1512
1655
  spin.text = "Gerando README e dashboard...";
@@ -1519,7 +1662,10 @@ function initCommand() {
1519
1662
  semestres: matrizOficial.semestres
1520
1663
  })
1521
1664
  );
1522
- await writeIfMissing(import_node_path16.default.join(root, ".gitignore"), GITIGNORE);
1665
+ await writeIfMissing(
1666
+ import_node_path16.default.join(root, ".gitignore"),
1667
+ GITIGNORE
1668
+ );
1523
1669
  await writeIfMissing(
1524
1670
  import_node_path16.default.join(root, "roadmap", "Roadmap.md"),
1525
1671
  ROADMAP
@@ -1530,14 +1676,20 @@ function initCommand() {
1530
1676
  spin.succeed("Vault criado com sucesso!");
1531
1677
  logger.blank();
1532
1678
  logger.success(`\u{1F4C1} ${root}`);
1533
- logger.item(`${totalDisciplinas} disciplinas em 6 semestres`);
1679
+ logger.item(
1680
+ `${totalDisciplinas} disciplinas em 6 semestres`
1681
+ );
1534
1682
  logger.item("Dashboard em dashboard/Dashboard.md");
1535
1683
  logger.item("Abra a pasta como vault no Obsidian \u{1F7E3}");
1536
1684
  if (!gitOk) {
1537
- logger.warn("Git n\xE3o encontrado \u2014 reposit\xF3rio n\xE3o inicializado.");
1685
+ logger.warn(
1686
+ "Git n\xE3o encontrado \u2014 reposit\xF3rio n\xE3o inicializado."
1687
+ );
1538
1688
  }
1539
1689
  logger.blank();
1540
- logger.info(`Pr\xF3ximo passo: cd ${pasta} && minerva dashboard`);
1690
+ logger.info(
1691
+ `Pr\xF3ximo passo: cd ${pasta} && minerva dashboard`
1692
+ );
1541
1693
  } catch (error) {
1542
1694
  spin.fail("Falha ao criar o vault.");
1543
1695
  throw error;
@@ -1632,7 +1784,9 @@ function notaCommand() {
1632
1784
  `${linha.disciplina}: m\xE9dia ${import_chalk2.default.bold(linha.media)} \u2014 ${color(linha.situacao)}`
1633
1785
  );
1634
1786
  for (const nota of linha.notas) {
1635
- logger.item(`${nota.avaliacao}: ${nota.valor} (peso ${nota.peso})`);
1787
+ logger.item(
1788
+ `${nota.avaliacao}: ${nota.valor} (peso ${nota.peso})`
1789
+ );
1636
1790
  }
1637
1791
  }
1638
1792
  });
@@ -1666,7 +1820,9 @@ function notaCommand() {
1666
1820
  peso: Number(options.peso)
1667
1821
  });
1668
1822
  const notas = await loadNotas(vault.root);
1669
- const linha = boletim(notas).find((b) => b.disciplina === disciplina);
1823
+ const linha = boletim(notas).find(
1824
+ (b) => b.disciplina === disciplina
1825
+ );
1670
1826
  logger.success(
1671
1827
  `${avaliacao} = ${valorFinal} registrada em ${disciplina}.`
1672
1828
  );
@@ -1741,7 +1897,9 @@ async function listPendencias(vault, matriz) {
1741
1897
  const stack = [dir];
1742
1898
  while (stack.length > 0) {
1743
1899
  const current = stack.pop();
1744
- for (const entry of await import_fs_extra14.default.readdir(current, { withFileTypes: true })) {
1900
+ for (const entry of await import_fs_extra14.default.readdir(current, {
1901
+ withFileTypes: true
1902
+ })) {
1745
1903
  const full = import_node_path19.default.join(current, entry.name);
1746
1904
  if (entry.isDirectory()) stack.push(full);
1747
1905
  else if (entry.name.endsWith(".md")) {
@@ -1841,9 +1999,15 @@ function portfolioCommand() {
1841
1999
  ),
1842
2000
  ""
1843
2001
  ];
1844
- const destination = import_node_path20.default.join(vault.root, "portfolio", "Portfolio.md");
2002
+ const destination = import_node_path20.default.join(
2003
+ vault.root,
2004
+ "portfolio",
2005
+ "Portfolio.md"
2006
+ );
1845
2007
  await import_fs_extra15.default.outputFile(destination, lines.join("\n"), "utf8");
1846
- spin.succeed(`Portf\xF3lio atualizado com ${items.length} projeto(s)!`);
2008
+ spin.succeed(
2009
+ `Portf\xF3lio atualizado com ${items.length} projeto(s)!`
2010
+ );
1847
2011
  logger.item(import_node_path20.default.relative(vault.root, destination));
1848
2012
  } catch (error) {
1849
2013
  spin.fail("Falha ao gerar o portf\xF3lio.");
@@ -1893,7 +2057,10 @@ async function proximosCompromissos(vault, matriz, dias = 7) {
1893
2057
  compromissos.push({
1894
2058
  tipo: spec.tipo,
1895
2059
  disciplina: disciplina.nome,
1896
- titulo: extractTitle(content, file.replace(/\.md$/, "")),
2060
+ titulo: extractTitle(
2061
+ content,
2062
+ file.replace(/\.md$/, "")
2063
+ ),
1897
2064
  data,
1898
2065
  arquivo: import_node_path21.default.relative(vault.root, full)
1899
2066
  });
@@ -1950,7 +2117,9 @@ function semestreCommand() {
1950
2117
  if (numero === void 0) {
1951
2118
  const atual = getSemestre(matriz, vault.config.semestreAtual);
1952
2119
  logger.title(`${atual.numero}\xBA semestre`);
1953
- logger.info(`Progresso do curso: ${progressBar(atual.numero - 1, 6)}`);
2120
+ logger.info(
2121
+ `Progresso do curso: ${progressBar(atual.numero - 1, 6)}`
2122
+ );
1954
2123
  for (const d of atual.disciplinas) {
1955
2124
  logger.item(`${d.nome} (${d.aulas} aulas)`);
1956
2125
  }
@@ -1979,8 +2148,11 @@ async function collectDates(root) {
1979
2148
  const stack = [root];
1980
2149
  while (stack.length > 0) {
1981
2150
  const current = stack.pop();
1982
- for (const entry of await import_fs_extra17.default.readdir(current, { withFileTypes: true })) {
1983
- if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
2151
+ for (const entry of await import_fs_extra17.default.readdir(current, {
2152
+ withFileTypes: true
2153
+ })) {
2154
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
2155
+ continue;
1984
2156
  const full = import_node_path22.default.join(current, entry.name);
1985
2157
  if (entry.isDirectory()) stack.push(full);
1986
2158
  else {
@@ -2036,7 +2208,9 @@ function statsCommand() {
2036
2208
  return;
2037
2209
  }
2038
2210
  logger.title("Suas estat\xEDsticas de estudo");
2039
- logger.info(`\u{1F525} Streak: ${import_chalk4.default.bold(stats.streak)} dia(s) seguidos`);
2211
+ logger.info(
2212
+ `\u{1F525} Streak: ${import_chalk4.default.bold(stats.streak)} dia(s) seguidos`
2213
+ );
2040
2214
  logger.info(`\u23F1\uFE0F Horas estudadas: ${stats.horasEstudadas}h`);
2041
2215
  logger.info(`\u{1F4DD} Notas criadas: ${stats.totalNotas}`);
2042
2216
  logger.blank();