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 +229 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +229 -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.1
|
|
12
|
+
var APP_VERSION = "0.2.1";
|
|
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,110 @@ ${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
|
+
const options = {
|
|
1234
|
+
num_predict: Number(process.env["OLLAMA_MAX_TOKENS"] ?? 1500)
|
|
1235
|
+
};
|
|
1236
|
+
if (process.env["OLLAMA_THREADS"]) {
|
|
1237
|
+
options["num_thread"] = Number(process.env["OLLAMA_THREADS"]);
|
|
1238
|
+
}
|
|
1239
|
+
return {
|
|
1240
|
+
name: `ollama (${model} @ ${baseUrl})`,
|
|
1241
|
+
async generate(prompt, material) {
|
|
1242
|
+
let response;
|
|
1243
|
+
try {
|
|
1244
|
+
response = await fetch(`${baseUrl}/api/chat`, {
|
|
1245
|
+
method: "POST",
|
|
1246
|
+
headers,
|
|
1247
|
+
body: JSON.stringify({
|
|
1248
|
+
model,
|
|
1249
|
+
stream: false,
|
|
1250
|
+
options,
|
|
1251
|
+
messages: [
|
|
1252
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
1253
|
+
{
|
|
1254
|
+
role: "user",
|
|
1255
|
+
content: `${prompt}
|
|
1256
|
+
|
|
1257
|
+
<material>
|
|
1258
|
+
${material}
|
|
1259
|
+
</material>`
|
|
1260
|
+
}
|
|
1261
|
+
]
|
|
1262
|
+
})
|
|
1263
|
+
});
|
|
1264
|
+
} catch (cause) {
|
|
1265
|
+
throw new Error(
|
|
1266
|
+
`N\xE3o consegui falar com o Ollama em ${baseUrl}.
|
|
1267
|
+
Local: instale com "curl -fsSL https://ollama.com/install.sh | sh" e rode "ollama serve".
|
|
1268
|
+
VPS: defina OLLAMA_URL apontando para o seu servidor.`,
|
|
1269
|
+
{ cause }
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
if (response.status === 404) {
|
|
1273
|
+
throw new Error(
|
|
1274
|
+
`O modelo "${model}" n\xE3o est\xE1 instalado no Ollama. Rode: ollama pull ${model}`
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
if (!response.ok) {
|
|
1278
|
+
throw new Error(
|
|
1279
|
+
`Ollama respondeu HTTP ${response.status}: ${await response.text()}`
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
const data = await response.json();
|
|
1283
|
+
const content = data.message?.content;
|
|
1284
|
+
if (!content) {
|
|
1285
|
+
throw new Error("O Ollama retornou uma resposta vazia.");
|
|
1286
|
+
}
|
|
1287
|
+
return content;
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
async function ollamaDisponivel() {
|
|
1292
|
+
const baseUrl = (process.env["OLLAMA_URL"] ?? OLLAMA_DEFAULT_URL).replace(
|
|
1293
|
+
/\/$/,
|
|
1294
|
+
""
|
|
1295
|
+
);
|
|
1296
|
+
const headers = {};
|
|
1297
|
+
if (process.env["OLLAMA_API_KEY"]) {
|
|
1298
|
+
headers["authorization"] = `Bearer ${process.env["OLLAMA_API_KEY"]}`;
|
|
1299
|
+
}
|
|
1300
|
+
try {
|
|
1301
|
+
const response = await fetch(`${baseUrl}/api/tags`, {
|
|
1302
|
+
headers,
|
|
1303
|
+
signal: AbortSignal.timeout(3e3)
|
|
1304
|
+
});
|
|
1305
|
+
return response.ok;
|
|
1306
|
+
} catch {
|
|
1307
|
+
return false;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
async function resolveProvider() {
|
|
1311
|
+
const forced = process.env["MINERVA_AI"]?.toLowerCase();
|
|
1312
|
+
if (forced === "anthropic") return anthropicProvider();
|
|
1313
|
+
if (forced === "ollama") return ollamaProvider();
|
|
1314
|
+
if (forced) {
|
|
1315
|
+
throw new Error(
|
|
1316
|
+
`Provedor desconhecido em MINERVA_AI: "${forced}". Use "anthropic" ou "ollama".`
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
if (process.env["ANTHROPIC_API_KEY"]) return anthropicProvider();
|
|
1320
|
+
if (await ollamaDisponivel()) return ollamaProvider();
|
|
1321
|
+
throw new Error(
|
|
1322
|
+
'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"'
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1185
1325
|
function resumoPrompt(disciplina, tema) {
|
|
1186
1326
|
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
1327
|
}
|
|
@@ -1206,7 +1346,9 @@ var DATE_HINT = "Use uma data v\xE1lida no formato YYYY-MM-DD.";
|
|
|
1206
1346
|
async function readMaterial(dir) {
|
|
1207
1347
|
if (!await fs11.pathExists(dir)) return "";
|
|
1208
1348
|
const parts = [];
|
|
1209
|
-
for (const file of (await fs11.readdir(dir)).filter(
|
|
1349
|
+
for (const file of (await fs11.readdir(dir)).filter(
|
|
1350
|
+
(f) => f.endsWith(".md")
|
|
1351
|
+
)) {
|
|
1210
1352
|
parts.push(await fs11.readFile(path14.join(dir, file), "utf8"));
|
|
1211
1353
|
}
|
|
1212
1354
|
return parts.join("\n\n---\n\n");
|
|
@@ -1222,7 +1364,7 @@ function noteCommand(spec) {
|
|
|
1222
1364
|
if (spec.ai) {
|
|
1223
1365
|
command.option(
|
|
1224
1366
|
"--ai",
|
|
1225
|
-
`gera o conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/ (
|
|
1367
|
+
`gera o conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/ (Anthropic ou Ollama)`
|
|
1226
1368
|
);
|
|
1227
1369
|
}
|
|
1228
1370
|
command.action(
|
|
@@ -1256,13 +1398,13 @@ function noteCommand(spec) {
|
|
|
1256
1398
|
}
|
|
1257
1399
|
let conteudoIA;
|
|
1258
1400
|
if (spec.ai && options["ai"]) {
|
|
1259
|
-
const provider =
|
|
1401
|
+
const provider = await resolveProvider();
|
|
1260
1402
|
const sourceDir = path14.join(
|
|
1261
1403
|
disciplinaDir(vault.root, semestre, disciplina),
|
|
1262
1404
|
spec.ai.sourceFolder
|
|
1263
1405
|
);
|
|
1264
1406
|
const spinAi = spinner(
|
|
1265
|
-
`Gerando conte\xFAdo com IA
|
|
1407
|
+
`Gerando conte\xFAdo com IA a partir de ${spec.ai.sourceFolder}/...`
|
|
1266
1408
|
).start();
|
|
1267
1409
|
try {
|
|
1268
1410
|
const material = await readMaterial(sourceDir);
|
|
@@ -1286,11 +1428,14 @@ function noteCommand(spec) {
|
|
|
1286
1428
|
extra
|
|
1287
1429
|
});
|
|
1288
1430
|
if (conteudoIA) {
|
|
1289
|
-
await fs11.appendFile(
|
|
1431
|
+
await fs11.appendFile(
|
|
1432
|
+
file,
|
|
1433
|
+
`
|
|
1290
1434
|
## Gerado por IA
|
|
1291
1435
|
|
|
1292
1436
|
${conteudoIA}
|
|
1293
|
-
`
|
|
1437
|
+
`
|
|
1438
|
+
);
|
|
1294
1439
|
}
|
|
1295
1440
|
spin.succeed(`Pronto! Nota criada em:`);
|
|
1296
1441
|
logger.item(path14.relative(vault.root, file));
|
|
@@ -1384,14 +1529,18 @@ function flashcardsCommand() {
|
|
|
1384
1529
|
);
|
|
1385
1530
|
const create = flashcardsNewCommand();
|
|
1386
1531
|
command.addCommand(create, { isDefault: true });
|
|
1387
|
-
command.command("export").description(
|
|
1532
|
+
command.command("export").description(
|
|
1533
|
+
"Exporta todos os flashcards para um TSV import\xE1vel no Anki"
|
|
1534
|
+
).action(async () => {
|
|
1388
1535
|
const vault = await requireVault();
|
|
1389
1536
|
const spin = spinner("Coletando flashcards do vault...").start();
|
|
1390
1537
|
try {
|
|
1391
1538
|
const { file, total } = await exportAnki(vault);
|
|
1392
1539
|
spin.succeed(`${total} flashcard(s) exportado(s)!`);
|
|
1393
1540
|
logger.item(path15.relative(vault.root, file));
|
|
1394
|
-
logger.info(
|
|
1541
|
+
logger.info(
|
|
1542
|
+
"No Anki: Arquivo \u2192 Importar e selecione esse arquivo."
|
|
1543
|
+
);
|
|
1395
1544
|
} catch (error) {
|
|
1396
1545
|
spin.fail("Falha na exporta\xE7\xE3o.");
|
|
1397
1546
|
throw error;
|
|
@@ -1442,7 +1591,10 @@ function initCommand() {
|
|
|
1442
1591
|
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
1592
|
async (pasta, options) => {
|
|
1444
1593
|
logger.title("Minerva CLI \u2014 vamos montar sua gradua\xE7\xE3o!");
|
|
1445
|
-
const aluno = options.aluno ?? await input3({
|
|
1594
|
+
const aluno = options.aluno ?? await input3({
|
|
1595
|
+
message: "Seu nome:",
|
|
1596
|
+
default: "Estudante"
|
|
1597
|
+
});
|
|
1446
1598
|
const semestreAtual = options.semestre !== void 0 ? Number(options.semestre) : await number2({
|
|
1447
1599
|
message: "Em qual semestre voc\xEA est\xE1? (1-6)",
|
|
1448
1600
|
default: 1,
|
|
@@ -1467,7 +1619,10 @@ function initCommand() {
|
|
|
1467
1619
|
spin.text = "Copiando templates...";
|
|
1468
1620
|
await scaffoldTemplates(root);
|
|
1469
1621
|
spin.text = "Criando semestres e disciplinas...";
|
|
1470
|
-
const totalDisciplinas = await scaffoldSemestres(
|
|
1622
|
+
const totalDisciplinas = await scaffoldSemestres(
|
|
1623
|
+
root,
|
|
1624
|
+
matrizOficial
|
|
1625
|
+
);
|
|
1471
1626
|
spin.text = "Preparando vault do Obsidian...";
|
|
1472
1627
|
await scaffoldObsidian(root);
|
|
1473
1628
|
spin.text = "Gerando README e dashboard...";
|
|
@@ -1480,7 +1635,10 @@ function initCommand() {
|
|
|
1480
1635
|
semestres: matrizOficial.semestres
|
|
1481
1636
|
})
|
|
1482
1637
|
);
|
|
1483
|
-
await writeIfMissing(
|
|
1638
|
+
await writeIfMissing(
|
|
1639
|
+
path16.join(root, ".gitignore"),
|
|
1640
|
+
GITIGNORE
|
|
1641
|
+
);
|
|
1484
1642
|
await writeIfMissing(
|
|
1485
1643
|
path16.join(root, "roadmap", "Roadmap.md"),
|
|
1486
1644
|
ROADMAP
|
|
@@ -1491,14 +1649,20 @@ function initCommand() {
|
|
|
1491
1649
|
spin.succeed("Vault criado com sucesso!");
|
|
1492
1650
|
logger.blank();
|
|
1493
1651
|
logger.success(`\u{1F4C1} ${root}`);
|
|
1494
|
-
logger.item(
|
|
1652
|
+
logger.item(
|
|
1653
|
+
`${totalDisciplinas} disciplinas em 6 semestres`
|
|
1654
|
+
);
|
|
1495
1655
|
logger.item("Dashboard em dashboard/Dashboard.md");
|
|
1496
1656
|
logger.item("Abra a pasta como vault no Obsidian \u{1F7E3}");
|
|
1497
1657
|
if (!gitOk) {
|
|
1498
|
-
logger.warn(
|
|
1658
|
+
logger.warn(
|
|
1659
|
+
"Git n\xE3o encontrado \u2014 reposit\xF3rio n\xE3o inicializado."
|
|
1660
|
+
);
|
|
1499
1661
|
}
|
|
1500
1662
|
logger.blank();
|
|
1501
|
-
logger.info(
|
|
1663
|
+
logger.info(
|
|
1664
|
+
`Pr\xF3ximo passo: cd ${pasta} && minerva dashboard`
|
|
1665
|
+
);
|
|
1502
1666
|
} catch (error) {
|
|
1503
1667
|
spin.fail("Falha ao criar o vault.");
|
|
1504
1668
|
throw error;
|
|
@@ -1593,7 +1757,9 @@ function notaCommand() {
|
|
|
1593
1757
|
`${linha.disciplina}: m\xE9dia ${chalk2.bold(linha.media)} \u2014 ${color(linha.situacao)}`
|
|
1594
1758
|
);
|
|
1595
1759
|
for (const nota of linha.notas) {
|
|
1596
|
-
logger.item(
|
|
1760
|
+
logger.item(
|
|
1761
|
+
`${nota.avaliacao}: ${nota.valor} (peso ${nota.peso})`
|
|
1762
|
+
);
|
|
1597
1763
|
}
|
|
1598
1764
|
}
|
|
1599
1765
|
});
|
|
@@ -1627,7 +1793,9 @@ function notaCommand() {
|
|
|
1627
1793
|
peso: Number(options.peso)
|
|
1628
1794
|
});
|
|
1629
1795
|
const notas = await loadNotas(vault.root);
|
|
1630
|
-
const linha = boletim(notas).find(
|
|
1796
|
+
const linha = boletim(notas).find(
|
|
1797
|
+
(b) => b.disciplina === disciplina
|
|
1798
|
+
);
|
|
1631
1799
|
logger.success(
|
|
1632
1800
|
`${avaliacao} = ${valorFinal} registrada em ${disciplina}.`
|
|
1633
1801
|
);
|
|
@@ -1702,7 +1870,9 @@ async function listPendencias(vault, matriz) {
|
|
|
1702
1870
|
const stack = [dir];
|
|
1703
1871
|
while (stack.length > 0) {
|
|
1704
1872
|
const current = stack.pop();
|
|
1705
|
-
for (const entry of await fs14.readdir(current, {
|
|
1873
|
+
for (const entry of await fs14.readdir(current, {
|
|
1874
|
+
withFileTypes: true
|
|
1875
|
+
})) {
|
|
1706
1876
|
const full = path19.join(current, entry.name);
|
|
1707
1877
|
if (entry.isDirectory()) stack.push(full);
|
|
1708
1878
|
else if (entry.name.endsWith(".md")) {
|
|
@@ -1802,9 +1972,15 @@ function portfolioCommand() {
|
|
|
1802
1972
|
),
|
|
1803
1973
|
""
|
|
1804
1974
|
];
|
|
1805
|
-
const destination = path20.join(
|
|
1975
|
+
const destination = path20.join(
|
|
1976
|
+
vault.root,
|
|
1977
|
+
"portfolio",
|
|
1978
|
+
"Portfolio.md"
|
|
1979
|
+
);
|
|
1806
1980
|
await fs15.outputFile(destination, lines.join("\n"), "utf8");
|
|
1807
|
-
spin.succeed(
|
|
1981
|
+
spin.succeed(
|
|
1982
|
+
`Portf\xF3lio atualizado com ${items.length} projeto(s)!`
|
|
1983
|
+
);
|
|
1808
1984
|
logger.item(path20.relative(vault.root, destination));
|
|
1809
1985
|
} catch (error) {
|
|
1810
1986
|
spin.fail("Falha ao gerar o portf\xF3lio.");
|
|
@@ -1854,7 +2030,10 @@ async function proximosCompromissos(vault, matriz, dias = 7) {
|
|
|
1854
2030
|
compromissos.push({
|
|
1855
2031
|
tipo: spec.tipo,
|
|
1856
2032
|
disciplina: disciplina.nome,
|
|
1857
|
-
titulo: extractTitle(
|
|
2033
|
+
titulo: extractTitle(
|
|
2034
|
+
content,
|
|
2035
|
+
file.replace(/\.md$/, "")
|
|
2036
|
+
),
|
|
1858
2037
|
data,
|
|
1859
2038
|
arquivo: path21.relative(vault.root, full)
|
|
1860
2039
|
});
|
|
@@ -1911,7 +2090,9 @@ function semestreCommand() {
|
|
|
1911
2090
|
if (numero === void 0) {
|
|
1912
2091
|
const atual = getSemestre(matriz, vault.config.semestreAtual);
|
|
1913
2092
|
logger.title(`${atual.numero}\xBA semestre`);
|
|
1914
|
-
logger.info(
|
|
2093
|
+
logger.info(
|
|
2094
|
+
`Progresso do curso: ${progressBar(atual.numero - 1, 6)}`
|
|
2095
|
+
);
|
|
1915
2096
|
for (const d of atual.disciplinas) {
|
|
1916
2097
|
logger.item(`${d.nome} (${d.aulas} aulas)`);
|
|
1917
2098
|
}
|
|
@@ -1940,8 +2121,11 @@ async function collectDates(root) {
|
|
|
1940
2121
|
const stack = [root];
|
|
1941
2122
|
while (stack.length > 0) {
|
|
1942
2123
|
const current = stack.pop();
|
|
1943
|
-
for (const entry of await fs17.readdir(current, {
|
|
1944
|
-
|
|
2124
|
+
for (const entry of await fs17.readdir(current, {
|
|
2125
|
+
withFileTypes: true
|
|
2126
|
+
})) {
|
|
2127
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
2128
|
+
continue;
|
|
1945
2129
|
const full = path22.join(current, entry.name);
|
|
1946
2130
|
if (entry.isDirectory()) stack.push(full);
|
|
1947
2131
|
else {
|
|
@@ -1997,7 +2181,9 @@ function statsCommand() {
|
|
|
1997
2181
|
return;
|
|
1998
2182
|
}
|
|
1999
2183
|
logger.title("Suas estat\xEDsticas de estudo");
|
|
2000
|
-
logger.info(
|
|
2184
|
+
logger.info(
|
|
2185
|
+
`\u{1F525} Streak: ${chalk4.bold(stats.streak)} dia(s) seguidos`
|
|
2186
|
+
);
|
|
2001
2187
|
logger.info(`\u23F1\uFE0F Horas estudadas: ${stats.horasEstudadas}h`);
|
|
2002
2188
|
logger.info(`\u{1F4DD} Notas criadas: ${stats.totalNotas}`);
|
|
2003
2189
|
logger.blank();
|