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 +217 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +217 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { Command as Command20 } from "commander";
|
|
|
9
9
|
// src/config/constants.ts
|
|
10
10
|
var APP_NAME = "minerva";
|
|
11
11
|
var APP_DESCRIPTION = "Organiza\xE7\xE3o acad\xEAmica para estudantes de ADS da FATEC \u{1F989}";
|
|
12
|
-
var APP_VERSION = "0.
|
|
12
|
+
var APP_VERSION = "0.2.0";
|
|
13
13
|
var DEFAULT_VAULT_DIRNAME = "fatec-ads";
|
|
14
14
|
var MATRIZ_URL = "https://fatectq.cps.sp.gov.br/matriz-curricular-ads/";
|
|
15
15
|
|
|
@@ -571,14 +571,20 @@ function disciplinaDir(root, semestre, disciplina) {
|
|
|
571
571
|
async function scaffoldDisciplina(root, semestre, disciplina) {
|
|
572
572
|
const dir = disciplinaDir(root, semestre, disciplina.nome);
|
|
573
573
|
await Promise.all(
|
|
574
|
-
DISCIPLINA_FOLDERS.map(
|
|
574
|
+
DISCIPLINA_FOLDERS.map(
|
|
575
|
+
(folder) => fs5.ensureDir(path6.join(dir, folder))
|
|
576
|
+
)
|
|
575
577
|
);
|
|
576
578
|
const files = [
|
|
577
579
|
[
|
|
578
580
|
"README.md",
|
|
579
581
|
() => renderTemplate(
|
|
580
582
|
"disciplina",
|
|
581
|
-
{
|
|
583
|
+
{
|
|
584
|
+
disciplina: disciplina.nome,
|
|
585
|
+
semestre,
|
|
586
|
+
aulas: disciplina.aulas
|
|
587
|
+
},
|
|
582
588
|
root
|
|
583
589
|
)
|
|
584
590
|
],
|
|
@@ -831,7 +837,9 @@ function doctorCommand() {
|
|
|
831
837
|
{
|
|
832
838
|
label: "Node.js",
|
|
833
839
|
run: () => {
|
|
834
|
-
const major = Number(
|
|
840
|
+
const major = Number(
|
|
841
|
+
process.versions.node.split(".")[0]
|
|
842
|
+
);
|
|
835
843
|
if (major < 20) {
|
|
836
844
|
throw new Error(
|
|
837
845
|
`v${process.versions.node} \u2014 atualize para a LTS (>= 20)`
|
|
@@ -848,7 +856,9 @@ function doctorCommand() {
|
|
|
848
856
|
label: "Vault Minerva",
|
|
849
857
|
run: () => {
|
|
850
858
|
if (!root) {
|
|
851
|
-
throw new Error(
|
|
859
|
+
throw new Error(
|
|
860
|
+
'n\xE3o encontrado \u2014 rode "minerva init"'
|
|
861
|
+
);
|
|
852
862
|
}
|
|
853
863
|
return Promise.resolve(root);
|
|
854
864
|
}
|
|
@@ -878,7 +888,9 @@ function doctorCommand() {
|
|
|
878
888
|
label: "Reposit\xF3rio Git",
|
|
879
889
|
run: async () => {
|
|
880
890
|
if (!await fs7.pathExists(path10.join(root, ".git"))) {
|
|
881
|
-
throw new Error(
|
|
891
|
+
throw new Error(
|
|
892
|
+
'ausente \u2014 rode "git init" no vault'
|
|
893
|
+
);
|
|
882
894
|
}
|
|
883
895
|
return "ok";
|
|
884
896
|
}
|
|
@@ -886,7 +898,9 @@ function doctorCommand() {
|
|
|
886
898
|
{
|
|
887
899
|
label: "Vault do Obsidian",
|
|
888
900
|
run: async () => {
|
|
889
|
-
if (!await fs7.pathExists(
|
|
901
|
+
if (!await fs7.pathExists(
|
|
902
|
+
path10.join(root, ".obsidian")
|
|
903
|
+
)) {
|
|
890
904
|
throw new Error(
|
|
891
905
|
'pasta .obsidian ausente \u2014 rode "minerva init" novamente'
|
|
892
906
|
);
|
|
@@ -939,7 +953,9 @@ async function loadEstudoLog(root) {
|
|
|
939
953
|
}
|
|
940
954
|
async function registrarEstudo(vault, horas, disciplina) {
|
|
941
955
|
if (!Number.isFinite(horas) || horas <= 0) {
|
|
942
|
-
throw new Error(
|
|
956
|
+
throw new Error(
|
|
957
|
+
"Informe um n\xFAmero de horas maior que zero (ex.: 1.5)."
|
|
958
|
+
);
|
|
943
959
|
}
|
|
944
960
|
const sessao = {
|
|
945
961
|
data: todayISO(),
|
|
@@ -985,12 +1001,16 @@ function estudoCommand() {
|
|
|
985
1001
|
);
|
|
986
1002
|
return;
|
|
987
1003
|
}
|
|
988
|
-
logger.title(
|
|
1004
|
+
logger.title(
|
|
1005
|
+
`${vault.config.horasEstudadas}h estudadas no total`
|
|
1006
|
+
);
|
|
989
1007
|
for (const [disciplina2, total] of totals) {
|
|
990
1008
|
logger.item(`${disciplina2}: ${total}h`);
|
|
991
1009
|
}
|
|
992
1010
|
if (totals.size === 0) {
|
|
993
|
-
logger.info(
|
|
1011
|
+
logger.info(
|
|
1012
|
+
"Registre sua primeira sess\xE3o: minerva estudo 1.5"
|
|
1013
|
+
);
|
|
994
1014
|
}
|
|
995
1015
|
return;
|
|
996
1016
|
}
|
|
@@ -1039,7 +1059,9 @@ async function collectMarkdownFiles(dir) {
|
|
|
1039
1059
|
const stack = [dir];
|
|
1040
1060
|
while (stack.length > 0) {
|
|
1041
1061
|
const current = stack.pop();
|
|
1042
|
-
for (const entry of await fs9.readdir(current, {
|
|
1062
|
+
for (const entry of await fs9.readdir(current, {
|
|
1063
|
+
withFileTypes: true
|
|
1064
|
+
})) {
|
|
1043
1065
|
const full = path12.join(current, entry.name);
|
|
1044
1066
|
if (entry.isDirectory()) stack.push(full);
|
|
1045
1067
|
else if (entry.name.endsWith(".md")) files.push(full);
|
|
@@ -1052,14 +1074,22 @@ async function collectAllFlashcards(vault) {
|
|
|
1052
1074
|
const stack = [vault.root];
|
|
1053
1075
|
while (stack.length > 0) {
|
|
1054
1076
|
const current = stack.pop();
|
|
1055
|
-
for (const entry of await fs9.readdir(current, {
|
|
1056
|
-
|
|
1077
|
+
for (const entry of await fs9.readdir(current, {
|
|
1078
|
+
withFileTypes: true
|
|
1079
|
+
})) {
|
|
1080
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
1081
|
+
continue;
|
|
1057
1082
|
const full = path12.join(current, entry.name);
|
|
1058
1083
|
if (!entry.isDirectory()) continue;
|
|
1059
1084
|
if (entry.name.toLowerCase() === "flashcards") {
|
|
1060
1085
|
const deck = path12.basename(path12.dirname(full));
|
|
1061
1086
|
for (const file of await collectMarkdownFiles(full)) {
|
|
1062
|
-
cards.push(
|
|
1087
|
+
cards.push(
|
|
1088
|
+
...parseFlashcards(
|
|
1089
|
+
await fs9.readFile(file, "utf8"),
|
|
1090
|
+
deck
|
|
1091
|
+
)
|
|
1092
|
+
);
|
|
1063
1093
|
}
|
|
1064
1094
|
} else {
|
|
1065
1095
|
stack.push(full);
|
|
@@ -1134,22 +1164,26 @@ async function createNote(vault, input5) {
|
|
|
1134
1164
|
|
|
1135
1165
|
// src/services/ai.ts
|
|
1136
1166
|
import Anthropic from "@anthropic-ai/sdk";
|
|
1137
|
-
var
|
|
1167
|
+
var ANTHROPIC_MODEL = "claude-opus-4-8";
|
|
1168
|
+
var OLLAMA_DEFAULT_MODEL = "llama3.2";
|
|
1169
|
+
var OLLAMA_DEFAULT_URL = "http://localhost:11434";
|
|
1170
|
+
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.";
|
|
1138
1171
|
function anthropicProvider() {
|
|
1139
1172
|
if (!process.env["ANTHROPIC_API_KEY"]) {
|
|
1140
1173
|
throw new Error(
|
|
1141
|
-
'Defina a vari\xE1vel de ambiente ANTHROPIC_API_KEY para usar
|
|
1174
|
+
'Defina a vari\xE1vel de ambiente ANTHROPIC_API_KEY para usar o provedor Anthropic.\n Ex.: export ANTHROPIC_API_KEY="sk-ant-..."'
|
|
1142
1175
|
);
|
|
1143
1176
|
}
|
|
1144
1177
|
const client = new Anthropic();
|
|
1145
1178
|
return {
|
|
1179
|
+
name: `anthropic (${ANTHROPIC_MODEL})`,
|
|
1146
1180
|
async generate(prompt, material) {
|
|
1147
1181
|
try {
|
|
1148
1182
|
const stream = client.messages.stream({
|
|
1149
|
-
model:
|
|
1183
|
+
model: ANTHROPIC_MODEL,
|
|
1150
1184
|
max_tokens: 64e3,
|
|
1151
1185
|
thinking: { type: "adaptive" },
|
|
1152
|
-
system:
|
|
1186
|
+
system: SYSTEM_PROMPT,
|
|
1153
1187
|
messages: [
|
|
1154
1188
|
{
|
|
1155
1189
|
role: "user",
|
|
@@ -1165,7 +1199,9 @@ ${material}
|
|
|
1165
1199
|
return message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
1166
1200
|
} catch (error) {
|
|
1167
1201
|
if (error instanceof Anthropic.AuthenticationError) {
|
|
1168
|
-
throw new Error(
|
|
1202
|
+
throw new Error(
|
|
1203
|
+
"ANTHROPIC_API_KEY inv\xE1lida. Verifique a chave."
|
|
1204
|
+
);
|
|
1169
1205
|
}
|
|
1170
1206
|
if (error instanceof Anthropic.RateLimitError) {
|
|
1171
1207
|
throw new Error(
|
|
@@ -1182,6 +1218,98 @@ ${material}
|
|
|
1182
1218
|
}
|
|
1183
1219
|
};
|
|
1184
1220
|
}
|
|
1221
|
+
function ollamaProvider() {
|
|
1222
|
+
const baseUrl = (process.env["OLLAMA_URL"] ?? OLLAMA_DEFAULT_URL).replace(
|
|
1223
|
+
/\/$/,
|
|
1224
|
+
""
|
|
1225
|
+
);
|
|
1226
|
+
const model = process.env["OLLAMA_MODEL"] ?? OLLAMA_DEFAULT_MODEL;
|
|
1227
|
+
const headers = {
|
|
1228
|
+
"content-type": "application/json"
|
|
1229
|
+
};
|
|
1230
|
+
if (process.env["OLLAMA_API_KEY"]) {
|
|
1231
|
+
headers["authorization"] = `Bearer ${process.env["OLLAMA_API_KEY"]}`;
|
|
1232
|
+
}
|
|
1233
|
+
return {
|
|
1234
|
+
name: `ollama (${model} @ ${baseUrl})`,
|
|
1235
|
+
async generate(prompt, material) {
|
|
1236
|
+
let response;
|
|
1237
|
+
try {
|
|
1238
|
+
response = await fetch(`${baseUrl}/api/chat`, {
|
|
1239
|
+
method: "POST",
|
|
1240
|
+
headers,
|
|
1241
|
+
body: JSON.stringify({
|
|
1242
|
+
model,
|
|
1243
|
+
stream: false,
|
|
1244
|
+
messages: [
|
|
1245
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
1246
|
+
{
|
|
1247
|
+
role: "user",
|
|
1248
|
+
content: `${prompt}
|
|
1249
|
+
|
|
1250
|
+
<material>
|
|
1251
|
+
${material}
|
|
1252
|
+
</material>`
|
|
1253
|
+
}
|
|
1254
|
+
]
|
|
1255
|
+
})
|
|
1256
|
+
});
|
|
1257
|
+
} catch (cause) {
|
|
1258
|
+
throw new Error(
|
|
1259
|
+
`N\xE3o consegui falar com o Ollama em ${baseUrl}.
|
|
1260
|
+
Local: instale com "curl -fsSL https://ollama.com/install.sh | sh" e rode "ollama serve".
|
|
1261
|
+
VPS: defina OLLAMA_URL apontando para o seu servidor.`,
|
|
1262
|
+
{ cause }
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
if (response.status === 404) {
|
|
1266
|
+
throw new Error(
|
|
1267
|
+
`O modelo "${model}" n\xE3o est\xE1 instalado no Ollama. Rode: ollama pull ${model}`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
if (!response.ok) {
|
|
1271
|
+
throw new Error(
|
|
1272
|
+
`Ollama respondeu HTTP ${response.status}: ${await response.text()}`
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
const data = await response.json();
|
|
1276
|
+
const content = data.message?.content;
|
|
1277
|
+
if (!content) {
|
|
1278
|
+
throw new Error("O Ollama retornou uma resposta vazia.");
|
|
1279
|
+
}
|
|
1280
|
+
return content;
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
async function ollamaDisponivel() {
|
|
1285
|
+
const baseUrl = (process.env["OLLAMA_URL"] ?? OLLAMA_DEFAULT_URL).replace(
|
|
1286
|
+
/\/$/,
|
|
1287
|
+
""
|
|
1288
|
+
);
|
|
1289
|
+
try {
|
|
1290
|
+
const response = await fetch(`${baseUrl}/api/tags`, {
|
|
1291
|
+
signal: AbortSignal.timeout(2e3)
|
|
1292
|
+
});
|
|
1293
|
+
return response.ok;
|
|
1294
|
+
} catch {
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
async function resolveProvider() {
|
|
1299
|
+
const forced = process.env["MINERVA_AI"]?.toLowerCase();
|
|
1300
|
+
if (forced === "anthropic") return anthropicProvider();
|
|
1301
|
+
if (forced === "ollama") return ollamaProvider();
|
|
1302
|
+
if (forced) {
|
|
1303
|
+
throw new Error(
|
|
1304
|
+
`Provedor desconhecido em MINERVA_AI: "${forced}". Use "anthropic" ou "ollama".`
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
if (process.env["ANTHROPIC_API_KEY"]) return anthropicProvider();
|
|
1308
|
+
if (await ollamaDisponivel()) return ollamaProvider();
|
|
1309
|
+
throw new Error(
|
|
1310
|
+
'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"'
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1185
1313
|
function resumoPrompt(disciplina, tema) {
|
|
1186
1314
|
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.`;
|
|
1187
1315
|
}
|
|
@@ -1206,7 +1334,9 @@ var DATE_HINT = "Use uma data v\xE1lida no formato YYYY-MM-DD.";
|
|
|
1206
1334
|
async function readMaterial(dir) {
|
|
1207
1335
|
if (!await fs11.pathExists(dir)) return "";
|
|
1208
1336
|
const parts = [];
|
|
1209
|
-
for (const file of (await fs11.readdir(dir)).filter(
|
|
1337
|
+
for (const file of (await fs11.readdir(dir)).filter(
|
|
1338
|
+
(f) => f.endsWith(".md")
|
|
1339
|
+
)) {
|
|
1210
1340
|
parts.push(await fs11.readFile(path14.join(dir, file), "utf8"));
|
|
1211
1341
|
}
|
|
1212
1342
|
return parts.join("\n\n---\n\n");
|
|
@@ -1222,7 +1352,7 @@ function noteCommand(spec) {
|
|
|
1222
1352
|
if (spec.ai) {
|
|
1223
1353
|
command.option(
|
|
1224
1354
|
"--ai",
|
|
1225
|
-
`gera o conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/ (
|
|
1355
|
+
`gera o conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/ (Anthropic ou Ollama)`
|
|
1226
1356
|
);
|
|
1227
1357
|
}
|
|
1228
1358
|
command.action(
|
|
@@ -1256,13 +1386,13 @@ function noteCommand(spec) {
|
|
|
1256
1386
|
}
|
|
1257
1387
|
let conteudoIA;
|
|
1258
1388
|
if (spec.ai && options["ai"]) {
|
|
1259
|
-
const provider =
|
|
1389
|
+
const provider = await resolveProvider();
|
|
1260
1390
|
const sourceDir = path14.join(
|
|
1261
1391
|
disciplinaDir(vault.root, semestre, disciplina),
|
|
1262
1392
|
spec.ai.sourceFolder
|
|
1263
1393
|
);
|
|
1264
1394
|
const spinAi = spinner(
|
|
1265
|
-
`Gerando conte\xFAdo com IA
|
|
1395
|
+
`Gerando conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/...`
|
|
1266
1396
|
).start();
|
|
1267
1397
|
try {
|
|
1268
1398
|
const material = await readMaterial(sourceDir);
|
|
@@ -1286,11 +1416,14 @@ function noteCommand(spec) {
|
|
|
1286
1416
|
extra
|
|
1287
1417
|
});
|
|
1288
1418
|
if (conteudoIA) {
|
|
1289
|
-
await fs11.appendFile(
|
|
1419
|
+
await fs11.appendFile(
|
|
1420
|
+
file,
|
|
1421
|
+
`
|
|
1290
1422
|
## Gerado por IA
|
|
1291
1423
|
|
|
1292
1424
|
${conteudoIA}
|
|
1293
|
-
`
|
|
1425
|
+
`
|
|
1426
|
+
);
|
|
1294
1427
|
}
|
|
1295
1428
|
spin.succeed(`Pronto! Nota criada em:`);
|
|
1296
1429
|
logger.item(path14.relative(vault.root, file));
|
|
@@ -1384,14 +1517,18 @@ function flashcardsCommand() {
|
|
|
1384
1517
|
);
|
|
1385
1518
|
const create = flashcardsNewCommand();
|
|
1386
1519
|
command.addCommand(create, { isDefault: true });
|
|
1387
|
-
command.command("export").description(
|
|
1520
|
+
command.command("export").description(
|
|
1521
|
+
"Exporta todos os flashcards para um TSV import\xE1vel no Anki"
|
|
1522
|
+
).action(async () => {
|
|
1388
1523
|
const vault = await requireVault();
|
|
1389
1524
|
const spin = spinner("Coletando flashcards do vault...").start();
|
|
1390
1525
|
try {
|
|
1391
1526
|
const { file, total } = await exportAnki(vault);
|
|
1392
1527
|
spin.succeed(`${total} flashcard(s) exportado(s)!`);
|
|
1393
1528
|
logger.item(path15.relative(vault.root, file));
|
|
1394
|
-
logger.info(
|
|
1529
|
+
logger.info(
|
|
1530
|
+
"No Anki: Arquivo \u2192 Importar e selecione esse arquivo."
|
|
1531
|
+
);
|
|
1395
1532
|
} catch (error) {
|
|
1396
1533
|
spin.fail("Falha na exporta\xE7\xE3o.");
|
|
1397
1534
|
throw error;
|
|
@@ -1442,7 +1579,10 @@ function initCommand() {
|
|
|
1442
1579
|
return new Command11("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(
|
|
1443
1580
|
async (pasta, options) => {
|
|
1444
1581
|
logger.title("Minerva CLI \u2014 vamos montar sua gradua\xE7\xE3o!");
|
|
1445
|
-
const aluno = options.aluno ?? await input3({
|
|
1582
|
+
const aluno = options.aluno ?? await input3({
|
|
1583
|
+
message: "Seu nome:",
|
|
1584
|
+
default: "Estudante"
|
|
1585
|
+
});
|
|
1446
1586
|
const semestreAtual = options.semestre !== void 0 ? Number(options.semestre) : await number2({
|
|
1447
1587
|
message: "Em qual semestre voc\xEA est\xE1? (1-6)",
|
|
1448
1588
|
default: 1,
|
|
@@ -1467,7 +1607,10 @@ function initCommand() {
|
|
|
1467
1607
|
spin.text = "Copiando templates...";
|
|
1468
1608
|
await scaffoldTemplates(root);
|
|
1469
1609
|
spin.text = "Criando semestres e disciplinas...";
|
|
1470
|
-
const totalDisciplinas = await scaffoldSemestres(
|
|
1610
|
+
const totalDisciplinas = await scaffoldSemestres(
|
|
1611
|
+
root,
|
|
1612
|
+
matrizOficial
|
|
1613
|
+
);
|
|
1471
1614
|
spin.text = "Preparando vault do Obsidian...";
|
|
1472
1615
|
await scaffoldObsidian(root);
|
|
1473
1616
|
spin.text = "Gerando README e dashboard...";
|
|
@@ -1480,7 +1623,10 @@ function initCommand() {
|
|
|
1480
1623
|
semestres: matrizOficial.semestres
|
|
1481
1624
|
})
|
|
1482
1625
|
);
|
|
1483
|
-
await writeIfMissing(
|
|
1626
|
+
await writeIfMissing(
|
|
1627
|
+
path16.join(root, ".gitignore"),
|
|
1628
|
+
GITIGNORE
|
|
1629
|
+
);
|
|
1484
1630
|
await writeIfMissing(
|
|
1485
1631
|
path16.join(root, "roadmap", "Roadmap.md"),
|
|
1486
1632
|
ROADMAP
|
|
@@ -1491,14 +1637,20 @@ function initCommand() {
|
|
|
1491
1637
|
spin.succeed("Vault criado com sucesso!");
|
|
1492
1638
|
logger.blank();
|
|
1493
1639
|
logger.success(`\u{1F4C1} ${root}`);
|
|
1494
|
-
logger.item(
|
|
1640
|
+
logger.item(
|
|
1641
|
+
`${totalDisciplinas} disciplinas em 6 semestres`
|
|
1642
|
+
);
|
|
1495
1643
|
logger.item("Dashboard em dashboard/Dashboard.md");
|
|
1496
1644
|
logger.item("Abra a pasta como vault no Obsidian \u{1F7E3}");
|
|
1497
1645
|
if (!gitOk) {
|
|
1498
|
-
logger.warn(
|
|
1646
|
+
logger.warn(
|
|
1647
|
+
"Git n\xE3o encontrado \u2014 reposit\xF3rio n\xE3o inicializado."
|
|
1648
|
+
);
|
|
1499
1649
|
}
|
|
1500
1650
|
logger.blank();
|
|
1501
|
-
logger.info(
|
|
1651
|
+
logger.info(
|
|
1652
|
+
`Pr\xF3ximo passo: cd ${pasta} && minerva dashboard`
|
|
1653
|
+
);
|
|
1502
1654
|
} catch (error) {
|
|
1503
1655
|
spin.fail("Falha ao criar o vault.");
|
|
1504
1656
|
throw error;
|
|
@@ -1593,7 +1745,9 @@ function notaCommand() {
|
|
|
1593
1745
|
`${linha.disciplina}: m\xE9dia ${chalk2.bold(linha.media)} \u2014 ${color(linha.situacao)}`
|
|
1594
1746
|
);
|
|
1595
1747
|
for (const nota of linha.notas) {
|
|
1596
|
-
logger.item(
|
|
1748
|
+
logger.item(
|
|
1749
|
+
`${nota.avaliacao}: ${nota.valor} (peso ${nota.peso})`
|
|
1750
|
+
);
|
|
1597
1751
|
}
|
|
1598
1752
|
}
|
|
1599
1753
|
});
|
|
@@ -1627,7 +1781,9 @@ function notaCommand() {
|
|
|
1627
1781
|
peso: Number(options.peso)
|
|
1628
1782
|
});
|
|
1629
1783
|
const notas = await loadNotas(vault.root);
|
|
1630
|
-
const linha = boletim(notas).find(
|
|
1784
|
+
const linha = boletim(notas).find(
|
|
1785
|
+
(b) => b.disciplina === disciplina
|
|
1786
|
+
);
|
|
1631
1787
|
logger.success(
|
|
1632
1788
|
`${avaliacao} = ${valorFinal} registrada em ${disciplina}.`
|
|
1633
1789
|
);
|
|
@@ -1702,7 +1858,9 @@ async function listPendencias(vault, matriz) {
|
|
|
1702
1858
|
const stack = [dir];
|
|
1703
1859
|
while (stack.length > 0) {
|
|
1704
1860
|
const current = stack.pop();
|
|
1705
|
-
for (const entry of await fs14.readdir(current, {
|
|
1861
|
+
for (const entry of await fs14.readdir(current, {
|
|
1862
|
+
withFileTypes: true
|
|
1863
|
+
})) {
|
|
1706
1864
|
const full = path19.join(current, entry.name);
|
|
1707
1865
|
if (entry.isDirectory()) stack.push(full);
|
|
1708
1866
|
else if (entry.name.endsWith(".md")) {
|
|
@@ -1802,9 +1960,15 @@ function portfolioCommand() {
|
|
|
1802
1960
|
),
|
|
1803
1961
|
""
|
|
1804
1962
|
];
|
|
1805
|
-
const destination = path20.join(
|
|
1963
|
+
const destination = path20.join(
|
|
1964
|
+
vault.root,
|
|
1965
|
+
"portfolio",
|
|
1966
|
+
"Portfolio.md"
|
|
1967
|
+
);
|
|
1806
1968
|
await fs15.outputFile(destination, lines.join("\n"), "utf8");
|
|
1807
|
-
spin.succeed(
|
|
1969
|
+
spin.succeed(
|
|
1970
|
+
`Portf\xF3lio atualizado com ${items.length} projeto(s)!`
|
|
1971
|
+
);
|
|
1808
1972
|
logger.item(path20.relative(vault.root, destination));
|
|
1809
1973
|
} catch (error) {
|
|
1810
1974
|
spin.fail("Falha ao gerar o portf\xF3lio.");
|
|
@@ -1854,7 +2018,10 @@ async function proximosCompromissos(vault, matriz, dias = 7) {
|
|
|
1854
2018
|
compromissos.push({
|
|
1855
2019
|
tipo: spec.tipo,
|
|
1856
2020
|
disciplina: disciplina.nome,
|
|
1857
|
-
titulo: extractTitle(
|
|
2021
|
+
titulo: extractTitle(
|
|
2022
|
+
content,
|
|
2023
|
+
file.replace(/\.md$/, "")
|
|
2024
|
+
),
|
|
1858
2025
|
data,
|
|
1859
2026
|
arquivo: path21.relative(vault.root, full)
|
|
1860
2027
|
});
|
|
@@ -1911,7 +2078,9 @@ function semestreCommand() {
|
|
|
1911
2078
|
if (numero === void 0) {
|
|
1912
2079
|
const atual = getSemestre(matriz, vault.config.semestreAtual);
|
|
1913
2080
|
logger.title(`${atual.numero}\xBA semestre`);
|
|
1914
|
-
logger.info(
|
|
2081
|
+
logger.info(
|
|
2082
|
+
`Progresso do curso: ${progressBar(atual.numero - 1, 6)}`
|
|
2083
|
+
);
|
|
1915
2084
|
for (const d of atual.disciplinas) {
|
|
1916
2085
|
logger.item(`${d.nome} (${d.aulas} aulas)`);
|
|
1917
2086
|
}
|
|
@@ -1940,8 +2109,11 @@ async function collectDates(root) {
|
|
|
1940
2109
|
const stack = [root];
|
|
1941
2110
|
while (stack.length > 0) {
|
|
1942
2111
|
const current = stack.pop();
|
|
1943
|
-
for (const entry of await fs17.readdir(current, {
|
|
1944
|
-
|
|
2112
|
+
for (const entry of await fs17.readdir(current, {
|
|
2113
|
+
withFileTypes: true
|
|
2114
|
+
})) {
|
|
2115
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
2116
|
+
continue;
|
|
1945
2117
|
const full = path22.join(current, entry.name);
|
|
1946
2118
|
if (entry.isDirectory()) stack.push(full);
|
|
1947
2119
|
else {
|
|
@@ -1997,7 +2169,9 @@ function statsCommand() {
|
|
|
1997
2169
|
return;
|
|
1998
2170
|
}
|
|
1999
2171
|
logger.title("Suas estat\xEDsticas de estudo");
|
|
2000
|
-
logger.info(
|
|
2172
|
+
logger.info(
|
|
2173
|
+
`\u{1F525} Streak: ${chalk4.bold(stats.streak)} dia(s) seguidos`
|
|
2174
|
+
);
|
|
2001
2175
|
logger.info(`\u23F1\uFE0F Horas estudadas: ${stats.horasEstudadas}h`);
|
|
2002
2176
|
logger.info(`\u{1F4DD} Notas criadas: ${stats.totalNotas}`);
|
|
2003
2177
|
logger.blank();
|