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.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
|
|
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(
|
|
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
|
-
{
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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, {
|
|
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, {
|
|
1095
|
-
|
|
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(
|
|
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
|
|
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
|
|
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:
|
|
1222
|
+
model: ANTHROPIC_MODEL,
|
|
1189
1223
|
max_tokens: 64e3,
|
|
1190
1224
|
thinking: { type: "adaptive" },
|
|
1191
|
-
system:
|
|
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(
|
|
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(
|
|
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}/ (
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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)({
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1697
|
+
logger.warn(
|
|
1698
|
+
"Git n\xE3o encontrado \u2014 reposit\xF3rio n\xE3o inicializado."
|
|
1699
|
+
);
|
|
1538
1700
|
}
|
|
1539
1701
|
logger.blank();
|
|
1540
|
-
logger.info(
|
|
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(
|
|
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(
|
|
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, {
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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, {
|
|
1983
|
-
|
|
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(
|
|
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();
|