minerva-cli 0.1.0 → 0.2.1

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.1";
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,110 @@ ${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
+ const options = {
1273
+ num_predict: Number(process.env["OLLAMA_MAX_TOKENS"] ?? 1500)
1274
+ };
1275
+ if (process.env["OLLAMA_THREADS"]) {
1276
+ options["num_thread"] = Number(process.env["OLLAMA_THREADS"]);
1277
+ }
1278
+ return {
1279
+ name: `ollama (${model} @ ${baseUrl})`,
1280
+ async generate(prompt, material) {
1281
+ let response;
1282
+ try {
1283
+ response = await fetch(`${baseUrl}/api/chat`, {
1284
+ method: "POST",
1285
+ headers,
1286
+ body: JSON.stringify({
1287
+ model,
1288
+ stream: false,
1289
+ options,
1290
+ messages: [
1291
+ { role: "system", content: SYSTEM_PROMPT },
1292
+ {
1293
+ role: "user",
1294
+ content: `${prompt}
1295
+
1296
+ <material>
1297
+ ${material}
1298
+ </material>`
1299
+ }
1300
+ ]
1301
+ })
1302
+ });
1303
+ } catch (cause) {
1304
+ throw new Error(
1305
+ `N\xE3o consegui falar com o Ollama em ${baseUrl}.
1306
+ Local: instale com "curl -fsSL https://ollama.com/install.sh | sh" e rode "ollama serve".
1307
+ VPS: defina OLLAMA_URL apontando para o seu servidor.`,
1308
+ { cause }
1309
+ );
1310
+ }
1311
+ if (response.status === 404) {
1312
+ throw new Error(
1313
+ `O modelo "${model}" n\xE3o est\xE1 instalado no Ollama. Rode: ollama pull ${model}`
1314
+ );
1315
+ }
1316
+ if (!response.ok) {
1317
+ throw new Error(
1318
+ `Ollama respondeu HTTP ${response.status}: ${await response.text()}`
1319
+ );
1320
+ }
1321
+ const data = await response.json();
1322
+ const content = data.message?.content;
1323
+ if (!content) {
1324
+ throw new Error("O Ollama retornou uma resposta vazia.");
1325
+ }
1326
+ return content;
1327
+ }
1328
+ };
1329
+ }
1330
+ async function ollamaDisponivel() {
1331
+ const baseUrl = (process.env["OLLAMA_URL"] ?? OLLAMA_DEFAULT_URL).replace(
1332
+ /\/$/,
1333
+ ""
1334
+ );
1335
+ const headers = {};
1336
+ if (process.env["OLLAMA_API_KEY"]) {
1337
+ headers["authorization"] = `Bearer ${process.env["OLLAMA_API_KEY"]}`;
1338
+ }
1339
+ try {
1340
+ const response = await fetch(`${baseUrl}/api/tags`, {
1341
+ headers,
1342
+ signal: AbortSignal.timeout(3e3)
1343
+ });
1344
+ return response.ok;
1345
+ } catch {
1346
+ return false;
1347
+ }
1348
+ }
1349
+ async function resolveProvider() {
1350
+ const forced = process.env["MINERVA_AI"]?.toLowerCase();
1351
+ if (forced === "anthropic") return anthropicProvider();
1352
+ if (forced === "ollama") return ollamaProvider();
1353
+ if (forced) {
1354
+ throw new Error(
1355
+ `Provedor desconhecido em MINERVA_AI: "${forced}". Use "anthropic" ou "ollama".`
1356
+ );
1357
+ }
1358
+ if (process.env["ANTHROPIC_API_KEY"]) return anthropicProvider();
1359
+ if (await ollamaDisponivel()) return ollamaProvider();
1360
+ throw new Error(
1361
+ '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"'
1362
+ );
1363
+ }
1224
1364
  function resumoPrompt(disciplina, tema) {
1225
1365
  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
1366
  }
@@ -1245,7 +1385,9 @@ var DATE_HINT = "Use uma data v\xE1lida no formato YYYY-MM-DD.";
1245
1385
  async function readMaterial(dir) {
1246
1386
  if (!await import_fs_extra11.default.pathExists(dir)) return "";
1247
1387
  const parts = [];
1248
- for (const file of (await import_fs_extra11.default.readdir(dir)).filter((f) => f.endsWith(".md"))) {
1388
+ for (const file of (await import_fs_extra11.default.readdir(dir)).filter(
1389
+ (f) => f.endsWith(".md")
1390
+ )) {
1249
1391
  parts.push(await import_fs_extra11.default.readFile(import_node_path14.default.join(dir, file), "utf8"));
1250
1392
  }
1251
1393
  return parts.join("\n\n---\n\n");
@@ -1261,7 +1403,7 @@ function noteCommand(spec) {
1261
1403
  if (spec.ai) {
1262
1404
  command.option(
1263
1405
  "--ai",
1264
- `gera o conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/ (requer ANTHROPIC_API_KEY)`
1406
+ `gera o conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/ (Anthropic ou Ollama)`
1265
1407
  );
1266
1408
  }
1267
1409
  command.action(
@@ -1295,13 +1437,13 @@ function noteCommand(spec) {
1295
1437
  }
1296
1438
  let conteudoIA;
1297
1439
  if (spec.ai && options["ai"]) {
1298
- const provider = anthropicProvider();
1440
+ const provider = await resolveProvider();
1299
1441
  const sourceDir = import_node_path14.default.join(
1300
1442
  disciplinaDir(vault.root, semestre, disciplina),
1301
1443
  spec.ai.sourceFolder
1302
1444
  );
1303
1445
  const spinAi = spinner(
1304
- `Gerando conte\xFAdo com IA (${spec.ai.sourceFolder}/)...`
1446
+ `Gerando conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/...`
1305
1447
  ).start();
1306
1448
  try {
1307
1449
  const material = await readMaterial(sourceDir);
@@ -1325,11 +1467,14 @@ function noteCommand(spec) {
1325
1467
  extra
1326
1468
  });
1327
1469
  if (conteudoIA) {
1328
- await import_fs_extra11.default.appendFile(file, `
1470
+ await import_fs_extra11.default.appendFile(
1471
+ file,
1472
+ `
1329
1473
  ## Gerado por IA
1330
1474
 
1331
1475
  ${conteudoIA}
1332
- `);
1476
+ `
1477
+ );
1333
1478
  }
1334
1479
  spin.succeed(`Pronto! Nota criada em:`);
1335
1480
  logger.item(import_node_path14.default.relative(vault.root, file));
@@ -1423,14 +1568,18 @@ function flashcardsCommand() {
1423
1568
  );
1424
1569
  const create = flashcardsNewCommand();
1425
1570
  command.addCommand(create, { isDefault: true });
1426
- command.command("export").description("Exporta todos os flashcards para um TSV import\xE1vel no Anki").action(async () => {
1571
+ command.command("export").description(
1572
+ "Exporta todos os flashcards para um TSV import\xE1vel no Anki"
1573
+ ).action(async () => {
1427
1574
  const vault = await requireVault();
1428
1575
  const spin = spinner("Coletando flashcards do vault...").start();
1429
1576
  try {
1430
1577
  const { file, total } = await exportAnki(vault);
1431
1578
  spin.succeed(`${total} flashcard(s) exportado(s)!`);
1432
1579
  logger.item(import_node_path15.default.relative(vault.root, file));
1433
- logger.info("No Anki: Arquivo \u2192 Importar e selecione esse arquivo.");
1580
+ logger.info(
1581
+ "No Anki: Arquivo \u2192 Importar e selecione esse arquivo."
1582
+ );
1434
1583
  } catch (error) {
1435
1584
  spin.fail("Falha na exporta\xE7\xE3o.");
1436
1585
  throw error;
@@ -1481,7 +1630,10 @@ function initCommand() {
1481
1630
  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
1631
  async (pasta, options) => {
1483
1632
  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" });
1633
+ const aluno = options.aluno ?? await (0, import_prompts8.input)({
1634
+ message: "Seu nome:",
1635
+ default: "Estudante"
1636
+ });
1485
1637
  const semestreAtual = options.semestre !== void 0 ? Number(options.semestre) : await (0, import_prompts8.number)({
1486
1638
  message: "Em qual semestre voc\xEA est\xE1? (1-6)",
1487
1639
  default: 1,
@@ -1506,7 +1658,10 @@ function initCommand() {
1506
1658
  spin.text = "Copiando templates...";
1507
1659
  await scaffoldTemplates(root);
1508
1660
  spin.text = "Criando semestres e disciplinas...";
1509
- const totalDisciplinas = await scaffoldSemestres(root, matrizOficial);
1661
+ const totalDisciplinas = await scaffoldSemestres(
1662
+ root,
1663
+ matrizOficial
1664
+ );
1510
1665
  spin.text = "Preparando vault do Obsidian...";
1511
1666
  await scaffoldObsidian(root);
1512
1667
  spin.text = "Gerando README e dashboard...";
@@ -1519,7 +1674,10 @@ function initCommand() {
1519
1674
  semestres: matrizOficial.semestres
1520
1675
  })
1521
1676
  );
1522
- await writeIfMissing(import_node_path16.default.join(root, ".gitignore"), GITIGNORE);
1677
+ await writeIfMissing(
1678
+ import_node_path16.default.join(root, ".gitignore"),
1679
+ GITIGNORE
1680
+ );
1523
1681
  await writeIfMissing(
1524
1682
  import_node_path16.default.join(root, "roadmap", "Roadmap.md"),
1525
1683
  ROADMAP
@@ -1530,14 +1688,20 @@ function initCommand() {
1530
1688
  spin.succeed("Vault criado com sucesso!");
1531
1689
  logger.blank();
1532
1690
  logger.success(`\u{1F4C1} ${root}`);
1533
- logger.item(`${totalDisciplinas} disciplinas em 6 semestres`);
1691
+ logger.item(
1692
+ `${totalDisciplinas} disciplinas em 6 semestres`
1693
+ );
1534
1694
  logger.item("Dashboard em dashboard/Dashboard.md");
1535
1695
  logger.item("Abra a pasta como vault no Obsidian \u{1F7E3}");
1536
1696
  if (!gitOk) {
1537
- logger.warn("Git n\xE3o encontrado \u2014 reposit\xF3rio n\xE3o inicializado.");
1697
+ logger.warn(
1698
+ "Git n\xE3o encontrado \u2014 reposit\xF3rio n\xE3o inicializado."
1699
+ );
1538
1700
  }
1539
1701
  logger.blank();
1540
- logger.info(`Pr\xF3ximo passo: cd ${pasta} && minerva dashboard`);
1702
+ logger.info(
1703
+ `Pr\xF3ximo passo: cd ${pasta} && minerva dashboard`
1704
+ );
1541
1705
  } catch (error) {
1542
1706
  spin.fail("Falha ao criar o vault.");
1543
1707
  throw error;
@@ -1632,7 +1796,9 @@ function notaCommand() {
1632
1796
  `${linha.disciplina}: m\xE9dia ${import_chalk2.default.bold(linha.media)} \u2014 ${color(linha.situacao)}`
1633
1797
  );
1634
1798
  for (const nota of linha.notas) {
1635
- logger.item(`${nota.avaliacao}: ${nota.valor} (peso ${nota.peso})`);
1799
+ logger.item(
1800
+ `${nota.avaliacao}: ${nota.valor} (peso ${nota.peso})`
1801
+ );
1636
1802
  }
1637
1803
  }
1638
1804
  });
@@ -1666,7 +1832,9 @@ function notaCommand() {
1666
1832
  peso: Number(options.peso)
1667
1833
  });
1668
1834
  const notas = await loadNotas(vault.root);
1669
- const linha = boletim(notas).find((b) => b.disciplina === disciplina);
1835
+ const linha = boletim(notas).find(
1836
+ (b) => b.disciplina === disciplina
1837
+ );
1670
1838
  logger.success(
1671
1839
  `${avaliacao} = ${valorFinal} registrada em ${disciplina}.`
1672
1840
  );
@@ -1741,7 +1909,9 @@ async function listPendencias(vault, matriz) {
1741
1909
  const stack = [dir];
1742
1910
  while (stack.length > 0) {
1743
1911
  const current = stack.pop();
1744
- for (const entry of await import_fs_extra14.default.readdir(current, { withFileTypes: true })) {
1912
+ for (const entry of await import_fs_extra14.default.readdir(current, {
1913
+ withFileTypes: true
1914
+ })) {
1745
1915
  const full = import_node_path19.default.join(current, entry.name);
1746
1916
  if (entry.isDirectory()) stack.push(full);
1747
1917
  else if (entry.name.endsWith(".md")) {
@@ -1841,9 +2011,15 @@ function portfolioCommand() {
1841
2011
  ),
1842
2012
  ""
1843
2013
  ];
1844
- const destination = import_node_path20.default.join(vault.root, "portfolio", "Portfolio.md");
2014
+ const destination = import_node_path20.default.join(
2015
+ vault.root,
2016
+ "portfolio",
2017
+ "Portfolio.md"
2018
+ );
1845
2019
  await import_fs_extra15.default.outputFile(destination, lines.join("\n"), "utf8");
1846
- spin.succeed(`Portf\xF3lio atualizado com ${items.length} projeto(s)!`);
2020
+ spin.succeed(
2021
+ `Portf\xF3lio atualizado com ${items.length} projeto(s)!`
2022
+ );
1847
2023
  logger.item(import_node_path20.default.relative(vault.root, destination));
1848
2024
  } catch (error) {
1849
2025
  spin.fail("Falha ao gerar o portf\xF3lio.");
@@ -1893,7 +2069,10 @@ async function proximosCompromissos(vault, matriz, dias = 7) {
1893
2069
  compromissos.push({
1894
2070
  tipo: spec.tipo,
1895
2071
  disciplina: disciplina.nome,
1896
- titulo: extractTitle(content, file.replace(/\.md$/, "")),
2072
+ titulo: extractTitle(
2073
+ content,
2074
+ file.replace(/\.md$/, "")
2075
+ ),
1897
2076
  data,
1898
2077
  arquivo: import_node_path21.default.relative(vault.root, full)
1899
2078
  });
@@ -1950,7 +2129,9 @@ function semestreCommand() {
1950
2129
  if (numero === void 0) {
1951
2130
  const atual = getSemestre(matriz, vault.config.semestreAtual);
1952
2131
  logger.title(`${atual.numero}\xBA semestre`);
1953
- logger.info(`Progresso do curso: ${progressBar(atual.numero - 1, 6)}`);
2132
+ logger.info(
2133
+ `Progresso do curso: ${progressBar(atual.numero - 1, 6)}`
2134
+ );
1954
2135
  for (const d of atual.disciplinas) {
1955
2136
  logger.item(`${d.nome} (${d.aulas} aulas)`);
1956
2137
  }
@@ -1979,8 +2160,11 @@ async function collectDates(root) {
1979
2160
  const stack = [root];
1980
2161
  while (stack.length > 0) {
1981
2162
  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;
2163
+ for (const entry of await import_fs_extra17.default.readdir(current, {
2164
+ withFileTypes: true
2165
+ })) {
2166
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
2167
+ continue;
1984
2168
  const full = import_node_path22.default.join(current, entry.name);
1985
2169
  if (entry.isDirectory()) stack.push(full);
1986
2170
  else {
@@ -2036,7 +2220,9 @@ function statsCommand() {
2036
2220
  return;
2037
2221
  }
2038
2222
  logger.title("Suas estat\xEDsticas de estudo");
2039
- logger.info(`\u{1F525} Streak: ${import_chalk4.default.bold(stats.streak)} dia(s) seguidos`);
2223
+ logger.info(
2224
+ `\u{1F525} Streak: ${import_chalk4.default.bold(stats.streak)} dia(s) seguidos`
2225
+ );
2040
2226
  logger.info(`\u23F1\uFE0F Horas estudadas: ${stats.horasEstudadas}h`);
2041
2227
  logger.info(`\u{1F4DD} Notas criadas: ${stats.totalNotas}`);
2042
2228
  logger.blank();