@wilm-ai/wilma-cli 0.0.11 → 1.1.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/README.md +42 -5
- package/dist/index.js +475 -24
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,11 +16,47 @@ wilma
|
|
|
16
16
|
wilmai
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
### Daily briefing
|
|
22
|
+
```bash
|
|
23
|
+
wilma summary [--days 7] [--student <id|name>] [--all-students] [--json]
|
|
24
|
+
```
|
|
25
|
+
Combines today's and tomorrow's schedule, upcoming exams, recent homework, news, and messages into one view. Designed for AI agents to surface what matters.
|
|
26
|
+
|
|
27
|
+
### Schedule
|
|
28
|
+
```bash
|
|
29
|
+
wilma schedule list [--when today|tomorrow|week] [--student <id|name>] [--all-students] [--json]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Homework
|
|
33
|
+
```bash
|
|
34
|
+
wilma homework list [--limit 10] [--student <id|name>] [--all-students] [--json]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Upcoming exams
|
|
38
|
+
```bash
|
|
39
|
+
wilma exams list [--limit 20] [--student <id|name>] [--all-students] [--json]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Exam grades
|
|
43
|
+
```bash
|
|
44
|
+
wilma grades list [--limit 20] [--student <id|name>] [--all-students] [--json]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### News and messages
|
|
48
|
+
```bash
|
|
49
|
+
wilma news list [--limit 20] [--student <id|name>] [--all-students] [--json]
|
|
50
|
+
wilma news read <id> [--student <id|name>] [--json]
|
|
51
|
+
wilma messages list [--folder inbox] [--limit 20] [--student <id|name>] [--all-students] [--json]
|
|
52
|
+
wilma messages read <id> [--student <id|name>] [--json]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Other
|
|
20
56
|
```bash
|
|
21
|
-
wilma kids list --json
|
|
22
|
-
wilma
|
|
23
|
-
wilma
|
|
57
|
+
wilma kids list [--json]
|
|
58
|
+
wilma update
|
|
59
|
+
wilma config clear
|
|
24
60
|
```
|
|
25
61
|
|
|
26
62
|
## Config
|
|
@@ -29,4 +65,5 @@ Use `wilma config clear` to remove it. Override with `WILMAI_CONFIG_PATH`.
|
|
|
29
65
|
|
|
30
66
|
## Notes
|
|
31
67
|
- Credentials are stored with lightweight obfuscation for convenience.
|
|
32
|
-
- For multi-child accounts, you can pass `--student <id>` or `--all`.
|
|
68
|
+
- For multi-child accounts, you can pass `--student <id|name>` or `--all-students`.
|
|
69
|
+
- All list commands support `--json` for agent-friendly structured output.
|
package/dist/index.js
CHANGED
|
@@ -11,8 +11,13 @@ if (process.stdin.isTTY) {
|
|
|
11
11
|
emitKeypressEvents(process.stdin);
|
|
12
12
|
}
|
|
13
13
|
const ACTIONS = [
|
|
14
|
+
{ value: "summary", name: "Daily summary" },
|
|
15
|
+
{ value: "schedule-today", name: "Today's schedule" },
|
|
16
|
+
{ value: "schedule-tomorrow", name: "Tomorrow's schedule" },
|
|
17
|
+
{ value: "homework", name: "Recent homework" },
|
|
18
|
+
{ value: "exams", name: "Upcoming exams" },
|
|
19
|
+
{ value: "grades", name: "Exam grades" },
|
|
14
20
|
{ value: "news", name: "List news" },
|
|
15
|
-
{ value: "exams", name: "List exams" },
|
|
16
21
|
{ value: "messages", name: "List messages" },
|
|
17
22
|
{ value: "exit", name: "Exit" },
|
|
18
23
|
];
|
|
@@ -140,6 +145,7 @@ async function runInteractive(config) {
|
|
|
140
145
|
const client = await WilmaClient.login(profile);
|
|
141
146
|
let nextAction = await selectOrCancel({
|
|
142
147
|
message: "What do you want to view?",
|
|
148
|
+
pageSize: 15,
|
|
143
149
|
choices: [
|
|
144
150
|
...ACTIONS.filter((a) => a.value !== "exit"),
|
|
145
151
|
{ value: "back", name: "Back to students" },
|
|
@@ -151,12 +157,32 @@ async function runInteractive(config) {
|
|
|
151
157
|
continue;
|
|
152
158
|
}
|
|
153
159
|
while (nextAction !== "exit" && nextAction !== "back") {
|
|
154
|
-
if (nextAction === "
|
|
155
|
-
|
|
160
|
+
if (nextAction === "summary") {
|
|
161
|
+
console.clear();
|
|
162
|
+
await outputSummary(client, { days: 7, json: false });
|
|
163
|
+
}
|
|
164
|
+
if (nextAction === "schedule-today") {
|
|
165
|
+
console.clear();
|
|
166
|
+
await outputSchedule(client, { when: "today", json: false });
|
|
167
|
+
}
|
|
168
|
+
if (nextAction === "schedule-tomorrow") {
|
|
169
|
+
console.clear();
|
|
170
|
+
await outputSchedule(client, { when: "tomorrow", json: false });
|
|
171
|
+
}
|
|
172
|
+
if (nextAction === "homework") {
|
|
173
|
+
console.clear();
|
|
174
|
+
await outputHomework(client, { limit: 10, json: false });
|
|
156
175
|
}
|
|
157
176
|
if (nextAction === "exams") {
|
|
158
177
|
console.clear();
|
|
159
|
-
await
|
|
178
|
+
await outputUpcomingExams(client, { limit: 20, json: false });
|
|
179
|
+
}
|
|
180
|
+
if (nextAction === "grades") {
|
|
181
|
+
console.clear();
|
|
182
|
+
await outputGrades(client, { limit: 20, json: false });
|
|
183
|
+
}
|
|
184
|
+
if (nextAction === "news") {
|
|
185
|
+
await selectNewsToRead(client);
|
|
160
186
|
}
|
|
161
187
|
if (nextAction === "messages") {
|
|
162
188
|
const folder = await selectOrCancel({
|
|
@@ -175,6 +201,7 @@ async function runInteractive(config) {
|
|
|
175
201
|
}
|
|
176
202
|
nextAction = await selectOrCancel({
|
|
177
203
|
message: "What next?",
|
|
204
|
+
pageSize: 15,
|
|
178
205
|
choices: [
|
|
179
206
|
...ACTIONS.filter((a) => a.value !== "exit"),
|
|
180
207
|
{ value: "back", name: "Back to students" },
|
|
@@ -365,6 +392,7 @@ async function handleCommand(args, config) {
|
|
|
365
392
|
}
|
|
366
393
|
if (command === "news") {
|
|
367
394
|
if (subcommand === "read" && flags.id) {
|
|
395
|
+
const newsId = parseReadId(flags.id, "news");
|
|
368
396
|
if (!flags.student) {
|
|
369
397
|
const students = await getStudentsForCommand(profile, config);
|
|
370
398
|
if (students.length > 1) {
|
|
@@ -382,7 +410,7 @@ async function handleCommand(args, config) {
|
|
|
382
410
|
...profile,
|
|
383
411
|
studentNumber: studentInfo?.studentNumber ?? profile.studentNumber,
|
|
384
412
|
});
|
|
385
|
-
await outputNewsItem(perStudentClient,
|
|
413
|
+
await outputNewsItem(perStudentClient, newsId, flags.json);
|
|
386
414
|
return;
|
|
387
415
|
}
|
|
388
416
|
if (flags.allStudents) {
|
|
@@ -407,6 +435,7 @@ async function handleCommand(args, config) {
|
|
|
407
435
|
}
|
|
408
436
|
if (command === "messages") {
|
|
409
437
|
if (subcommand === "read" && flags.id) {
|
|
438
|
+
const messageId = parseReadId(flags.id, "message");
|
|
410
439
|
if (!flags.student) {
|
|
411
440
|
const students = await getStudentsForCommand(profile, config);
|
|
412
441
|
if (students.length > 1) {
|
|
@@ -424,7 +453,7 @@ async function handleCommand(args, config) {
|
|
|
424
453
|
...profile,
|
|
425
454
|
studentNumber: studentInfo?.studentNumber ?? profile.studentNumber,
|
|
426
455
|
});
|
|
427
|
-
await outputMessageItem(perStudentClient,
|
|
456
|
+
await outputMessageItem(perStudentClient, messageId, flags.json);
|
|
428
457
|
return;
|
|
429
458
|
}
|
|
430
459
|
if (flags.allStudents) {
|
|
@@ -466,33 +495,111 @@ async function handleCommand(args, config) {
|
|
|
466
495
|
...profile,
|
|
467
496
|
studentNumber: studentInfo?.studentNumber ?? profile.studentNumber,
|
|
468
497
|
});
|
|
469
|
-
await
|
|
498
|
+
await outputUpcomingExams(perStudentClient, {
|
|
470
499
|
limit: flags.limit ?? 20,
|
|
471
500
|
json: flags.json,
|
|
472
501
|
label: studentInfo?.name ?? undefined,
|
|
473
502
|
});
|
|
474
503
|
return;
|
|
475
504
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
505
|
+
if (command === "schedule") {
|
|
506
|
+
if (flags.allStudents) {
|
|
507
|
+
await outputAllOverviewCommand(profile, config, "schedule", flags);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const studentInfo = await resolveStudentForFlags(profile, config, flags.student);
|
|
511
|
+
if (!studentInfo && !profile.studentNumber) {
|
|
512
|
+
await printStudentSelectionHelp(profile, config);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const perStudentClient = await WilmaClient.login({
|
|
516
|
+
...profile,
|
|
517
|
+
studentNumber: studentInfo?.studentNumber ?? profile.studentNumber,
|
|
518
|
+
});
|
|
519
|
+
await outputSchedule(perStudentClient, {
|
|
520
|
+
when: flags.when ?? "week",
|
|
521
|
+
json: flags.json,
|
|
522
|
+
label: studentInfo?.name ?? undefined,
|
|
523
|
+
});
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (command === "homework") {
|
|
527
|
+
if (flags.allStudents) {
|
|
528
|
+
await outputAllOverviewCommand(profile, config, "homework", flags);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const studentInfo = await resolveStudentForFlags(profile, config, flags.student);
|
|
532
|
+
if (!studentInfo && !profile.studentNumber) {
|
|
533
|
+
await printStudentSelectionHelp(profile, config);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const perStudentClient = await WilmaClient.login({
|
|
537
|
+
...profile,
|
|
538
|
+
studentNumber: studentInfo?.studentNumber ?? profile.studentNumber,
|
|
539
|
+
});
|
|
540
|
+
await outputHomework(perStudentClient, {
|
|
541
|
+
limit: flags.limit ?? 10,
|
|
542
|
+
json: flags.json,
|
|
543
|
+
label: studentInfo?.name ?? undefined,
|
|
544
|
+
});
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (command === "grades") {
|
|
548
|
+
if (flags.allStudents) {
|
|
549
|
+
await outputAllOverviewCommand(profile, config, "grades", flags);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const studentInfo = await resolveStudentForFlags(profile, config, flags.student);
|
|
553
|
+
if (!studentInfo && !profile.studentNumber) {
|
|
554
|
+
await printStudentSelectionHelp(profile, config);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const perStudentClient = await WilmaClient.login({
|
|
558
|
+
...profile,
|
|
559
|
+
studentNumber: studentInfo?.studentNumber ?? profile.studentNumber,
|
|
560
|
+
});
|
|
561
|
+
await outputGrades(perStudentClient, {
|
|
562
|
+
limit: flags.limit ?? 20,
|
|
563
|
+
json: flags.json,
|
|
564
|
+
label: studentInfo?.name ?? undefined,
|
|
565
|
+
});
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (command === "summary") {
|
|
569
|
+
if (flags.allStudents) {
|
|
570
|
+
await outputAllOverviewCommand(profile, config, "summary", flags);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const studentInfo = await resolveStudentForFlags(profile, config, flags.student);
|
|
574
|
+
if (!studentInfo && !profile.studentNumber) {
|
|
575
|
+
await printStudentSelectionHelp(profile, config);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const perStudentClient = await WilmaClient.login({
|
|
579
|
+
...profile,
|
|
580
|
+
studentNumber: studentInfo?.studentNumber ?? profile.studentNumber,
|
|
581
|
+
});
|
|
582
|
+
await outputSummary(perStudentClient, {
|
|
583
|
+
days: flags.days ?? 7,
|
|
584
|
+
json: flags.json,
|
|
585
|
+
label: studentInfo?.name ?? undefined,
|
|
586
|
+
});
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
printUsage();
|
|
487
590
|
}
|
|
488
591
|
function printUsage() {
|
|
489
592
|
console.log("Usage:");
|
|
593
|
+
console.log(" wilma summary [--days 7] [--student <id|name>] [--all-students] [--json]");
|
|
594
|
+
console.log(" wilma schedule list [--when today|tomorrow|week] [--student <id|name>] [--all-students] [--json]");
|
|
595
|
+
console.log(" wilma homework list [--limit 10] [--student <id|name>] [--all-students] [--json]");
|
|
596
|
+
console.log(" wilma exams list [--limit 20] [--student <id|name>] [--all-students] [--json]");
|
|
597
|
+
console.log(" wilma grades list [--limit 20] [--student <id|name>] [--all-students] [--json]");
|
|
490
598
|
console.log(" wilma kids list [--json]");
|
|
491
599
|
console.log(" wilma news list [--limit 20] [--student <id|name>] [--all-students] [--json]");
|
|
492
600
|
console.log(" wilma news read <id> [--student <id|name>] [--json]");
|
|
493
601
|
console.log(" wilma messages list [--folder inbox] [--limit 20] [--student <id|name>] [--all-students] [--json]");
|
|
494
602
|
console.log(" wilma messages read <id> [--student <id|name>] [--json]");
|
|
495
|
-
console.log(" wilma exams list [--limit 20] [--student <id|name>] [--all-students] [--json]");
|
|
496
603
|
console.log(" wilma update");
|
|
497
604
|
console.log(" wilma config clear");
|
|
498
605
|
console.log(" wilma --help | -h");
|
|
@@ -522,7 +629,10 @@ async function getProfileForCommandNonInteractive(config, flags) {
|
|
|
522
629
|
};
|
|
523
630
|
}
|
|
524
631
|
function parseArgs(args) {
|
|
525
|
-
const [command,
|
|
632
|
+
const [command, rawSubcommand, ...rawRest] = args;
|
|
633
|
+
// If "subcommand" is actually a flag, push it back into rest
|
|
634
|
+
const subcommand = rawSubcommand?.startsWith("--") ? undefined : rawSubcommand;
|
|
635
|
+
const rest = rawSubcommand?.startsWith("--") ? [rawSubcommand, ...rawRest] : rawRest;
|
|
526
636
|
const flags = {};
|
|
527
637
|
let i = 0;
|
|
528
638
|
while (i < rest.length) {
|
|
@@ -560,6 +670,19 @@ function parseArgs(args) {
|
|
|
560
670
|
i += 2;
|
|
561
671
|
continue;
|
|
562
672
|
}
|
|
673
|
+
if (arg === "--when") {
|
|
674
|
+
flags.when = rest[i + 1];
|
|
675
|
+
i += 2;
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
if (arg === "--days") {
|
|
679
|
+
const value = Number(rest[i + 1]);
|
|
680
|
+
if (!Number.isNaN(value)) {
|
|
681
|
+
flags.days = value;
|
|
682
|
+
}
|
|
683
|
+
i += 2;
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
563
686
|
if (!flags.id && !arg.startsWith("--")) {
|
|
564
687
|
flags.id = arg;
|
|
565
688
|
i += 1;
|
|
@@ -569,6 +692,18 @@ function parseArgs(args) {
|
|
|
569
692
|
}
|
|
570
693
|
return { command, subcommand, flags };
|
|
571
694
|
}
|
|
695
|
+
function parseReadId(raw, entity) {
|
|
696
|
+
if (!raw) {
|
|
697
|
+
console.error(`Missing ${entity} id.`);
|
|
698
|
+
process.exit(1);
|
|
699
|
+
}
|
|
700
|
+
const id = Number(raw);
|
|
701
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
702
|
+
console.error(`Invalid ${entity} id "${raw}". Expected a positive integer.`);
|
|
703
|
+
process.exit(1);
|
|
704
|
+
}
|
|
705
|
+
return id;
|
|
706
|
+
}
|
|
572
707
|
async function readPackageVersion() {
|
|
573
708
|
const pkgPath = resolve(dirname(new URL(import.meta.url).pathname), "..", "package.json");
|
|
574
709
|
const raw = await readFile(pkgPath, "utf-8");
|
|
@@ -726,6 +861,228 @@ async function outputExams(client, opts) {
|
|
|
726
861
|
console.log(`- ${prefix}${date} ${compactText(exam.subject)}`);
|
|
727
862
|
});
|
|
728
863
|
}
|
|
864
|
+
/* ------------------------------------------------------------------ */
|
|
865
|
+
/* Overview-powered output functions */
|
|
866
|
+
/* ------------------------------------------------------------------ */
|
|
867
|
+
function todayString() {
|
|
868
|
+
const d = new Date();
|
|
869
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
870
|
+
}
|
|
871
|
+
function nextSchoolDay(from) {
|
|
872
|
+
const d = from ? new Date(from + "T12:00:00") : new Date();
|
|
873
|
+
d.setDate(d.getDate() + 1);
|
|
874
|
+
// Skip Saturday (6) and Sunday (0)
|
|
875
|
+
while (d.getDay() === 0 || d.getDay() === 6) {
|
|
876
|
+
d.setDate(d.getDate() + 1);
|
|
877
|
+
}
|
|
878
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
879
|
+
}
|
|
880
|
+
function currentWeekBounds() {
|
|
881
|
+
const now = new Date();
|
|
882
|
+
const dayOfWeek = now.getDay(); // 0=Sun, 1=Mon, ...
|
|
883
|
+
const diffToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
|
|
884
|
+
const monday = new Date(now);
|
|
885
|
+
monday.setDate(now.getDate() + diffToMonday);
|
|
886
|
+
const friday = new Date(monday);
|
|
887
|
+
friday.setDate(monday.getDate() + 4);
|
|
888
|
+
const fmt = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
889
|
+
return [fmt(monday), fmt(friday)];
|
|
890
|
+
}
|
|
891
|
+
const DAY_NAMES = ["Su", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
892
|
+
async function outputSchedule(client, opts) {
|
|
893
|
+
const overview = await client.overview.get();
|
|
894
|
+
const when = opts.when || "week";
|
|
895
|
+
let startDate;
|
|
896
|
+
let endDate;
|
|
897
|
+
if (when === "today") {
|
|
898
|
+
startDate = endDate = todayString();
|
|
899
|
+
}
|
|
900
|
+
else if (when === "tomorrow") {
|
|
901
|
+
startDate = endDate = nextSchoolDay();
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
[startDate, endDate] = currentWeekBounds();
|
|
905
|
+
}
|
|
906
|
+
const lessons = overview.schedule.filter((l) => l.date >= startDate && l.date <= endDate);
|
|
907
|
+
if (opts.json) {
|
|
908
|
+
const result = when === "week"
|
|
909
|
+
? { when, weekStart: startDate, weekEnd: endDate, lessons }
|
|
910
|
+
: { when, date: startDate, lessons };
|
|
911
|
+
console.log(JSON.stringify(result, null, 2));
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const prefix = opts.label ? `[${opts.label}] ` : "";
|
|
915
|
+
if (when === "today") {
|
|
916
|
+
console.log(`\n${prefix}Schedule for today (${startDate})`);
|
|
917
|
+
}
|
|
918
|
+
else if (when === "tomorrow") {
|
|
919
|
+
console.log(`\n${prefix}Schedule for tomorrow (${startDate})`);
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
console.log(`\n${prefix}Schedule for ${startDate} – ${endDate}`);
|
|
923
|
+
}
|
|
924
|
+
if (!lessons.length) {
|
|
925
|
+
console.log(" No lessons found.");
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
let currentDate = "";
|
|
929
|
+
for (const l of lessons) {
|
|
930
|
+
if (l.date !== currentDate) {
|
|
931
|
+
currentDate = l.date;
|
|
932
|
+
const d = new Date(l.date + "T12:00:00");
|
|
933
|
+
console.log(` ${DAY_NAMES[d.getDay()]} ${l.date}`);
|
|
934
|
+
}
|
|
935
|
+
const teacher = l.teacherCode ? ` - ${l.teacher}` : "";
|
|
936
|
+
console.log(` ${l.start}-${l.end} ${l.subject}${teacher}`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
async function outputHomework(client, opts) {
|
|
940
|
+
const overview = await client.overview.get();
|
|
941
|
+
const slice = overview.homework.slice(0, opts.limit);
|
|
942
|
+
if (opts.json) {
|
|
943
|
+
console.log(JSON.stringify(slice, null, 2));
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const prefix = opts.label ? `[${opts.label}] ` : "";
|
|
947
|
+
console.log(`\n${prefix}Homework (${overview.homework.length})`);
|
|
948
|
+
slice.forEach((hw) => {
|
|
949
|
+
const text = compactText(hw.homework);
|
|
950
|
+
console.log(`- ${hw.date} ${hw.subject}: ${text}`);
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
async function outputUpcomingExams(client, opts) {
|
|
954
|
+
const overview = await client.overview.get();
|
|
955
|
+
const slice = overview.upcomingExams.slice(0, opts.limit);
|
|
956
|
+
if (opts.json) {
|
|
957
|
+
console.log(JSON.stringify(slice, null, 2));
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
const prefix = opts.label ? `[${opts.label}] ` : "";
|
|
961
|
+
console.log(`\n${prefix}Upcoming exams (${overview.upcomingExams.length})`);
|
|
962
|
+
slice.forEach((exam) => {
|
|
963
|
+
const topic = exam.topic ? ` — ${compactText(exam.topic)}` : "";
|
|
964
|
+
console.log(`- ${exam.date} ${exam.subject}: ${exam.name}${topic}`);
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
async function outputGrades(client, opts) {
|
|
968
|
+
const overview = await client.overview.get();
|
|
969
|
+
const slice = overview.grades.slice(0, opts.limit);
|
|
970
|
+
if (opts.json) {
|
|
971
|
+
console.log(JSON.stringify(slice, null, 2));
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
const prefix = opts.label ? `[${opts.label}] ` : "";
|
|
975
|
+
console.log(`\n${prefix}Grades (${overview.grades.length})`);
|
|
976
|
+
slice.forEach((g) => {
|
|
977
|
+
console.log(`- ${g.date} ${g.subject}: ${g.name} — ${g.grade}`);
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
async function outputSummary(client, opts) {
|
|
981
|
+
const [overview, news, messages] = await Promise.all([
|
|
982
|
+
client.overview.get(),
|
|
983
|
+
client.news.list(),
|
|
984
|
+
client.messages.list("inbox"),
|
|
985
|
+
]);
|
|
986
|
+
const summary = buildSummaryData(overview, news, messages, opts.days, opts.label);
|
|
987
|
+
if (opts.json) {
|
|
988
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const label = summary.student ? ` for ${summary.student}` : "";
|
|
992
|
+
console.log(`\nSummary${label} (${summary.today})`);
|
|
993
|
+
console.log(`\nTODAY (${summary.today})`);
|
|
994
|
+
if (summary.todaySchedule.length) {
|
|
995
|
+
summary.todaySchedule.forEach((l) => {
|
|
996
|
+
console.log(` ${l.start}-${l.end} ${l.subject} (${l.subjectCode})`);
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
else {
|
|
1000
|
+
console.log(" No lessons today.");
|
|
1001
|
+
}
|
|
1002
|
+
console.log(`\nTOMORROW (${summary.tomorrow})`);
|
|
1003
|
+
if (summary.tomorrowSchedule.length) {
|
|
1004
|
+
summary.tomorrowSchedule.forEach((l) => {
|
|
1005
|
+
console.log(` ${l.start}-${l.end} ${l.subject} (${l.subjectCode})`);
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
console.log(" No lessons tomorrow.");
|
|
1010
|
+
}
|
|
1011
|
+
if (summary.upcomingExams.length) {
|
|
1012
|
+
console.log("\nUPCOMING EXAMS");
|
|
1013
|
+
summary.upcomingExams.forEach((exam) => {
|
|
1014
|
+
const topic = exam.topic ? ` — ${compactText(exam.topic)}` : "";
|
|
1015
|
+
console.log(` ${exam.date} ${exam.subject}: ${exam.name}${topic}`);
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
if (summary.recentHomework.length) {
|
|
1019
|
+
console.log("\nRECENT HOMEWORK");
|
|
1020
|
+
summary.recentHomework.forEach((hw) => {
|
|
1021
|
+
console.log(` ${hw.date} ${hw.subject}: ${compactText(hw.homework)}`);
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
if (summary.recentNews.length) {
|
|
1025
|
+
console.log(`\nNEWS (last ${opts.days} days)`);
|
|
1026
|
+
summary.recentNews.forEach((n) => {
|
|
1027
|
+
const date = n.published ? n.published.slice(0, 10) : "";
|
|
1028
|
+
console.log(` ${date} ${compactText(n.title)} (id:${n.wilmaId})`);
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
if (summary.recentMessages.length) {
|
|
1032
|
+
console.log(`\nMESSAGES (last ${opts.days} days)`);
|
|
1033
|
+
summary.recentMessages.forEach((m) => {
|
|
1034
|
+
const date = m.sentAt.slice(0, 10);
|
|
1035
|
+
console.log(` ${date} ${compactText(m.subject)} (id:${m.wilmaId})`);
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function buildSummaryData(overview, news, messages, days, studentLabel) {
|
|
1040
|
+
const today = todayString();
|
|
1041
|
+
const tomorrow = nextSchoolDay();
|
|
1042
|
+
const cutoffDate = (() => {
|
|
1043
|
+
const d = new Date();
|
|
1044
|
+
d.setDate(d.getDate() - days);
|
|
1045
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
1046
|
+
})();
|
|
1047
|
+
const homeworkCutoff = (() => {
|
|
1048
|
+
const d = new Date();
|
|
1049
|
+
d.setDate(d.getDate() - 3);
|
|
1050
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
1051
|
+
})();
|
|
1052
|
+
const todaySchedule = overview.schedule.filter((l) => l.date === today);
|
|
1053
|
+
const tomorrowSchedule = overview.schedule.filter((l) => l.date === tomorrow);
|
|
1054
|
+
const upcomingExams = overview.upcomingExams;
|
|
1055
|
+
const recentHomework = overview.homework.filter((h) => h.date >= homeworkCutoff);
|
|
1056
|
+
const recentNews = news
|
|
1057
|
+
.filter((n) => n.published && n.published.toISOString().slice(0, 10) >= cutoffDate)
|
|
1058
|
+
.slice(0, 5)
|
|
1059
|
+
.map((n) => ({
|
|
1060
|
+
wilmaId: n.wilmaId,
|
|
1061
|
+
title: n.title,
|
|
1062
|
+
published: n.published?.toISOString() ?? null,
|
|
1063
|
+
}));
|
|
1064
|
+
const recentMessages = messages
|
|
1065
|
+
.filter((m) => m.sentAt.toISOString().slice(0, 10) >= cutoffDate)
|
|
1066
|
+
.slice(0, 5)
|
|
1067
|
+
.map((m) => ({
|
|
1068
|
+
wilmaId: m.wilmaId,
|
|
1069
|
+
subject: m.subject,
|
|
1070
|
+
sentAt: m.sentAt.toISOString(),
|
|
1071
|
+
senderName: m.senderName ?? null,
|
|
1072
|
+
}));
|
|
1073
|
+
return {
|
|
1074
|
+
generatedAt: new Date().toISOString(),
|
|
1075
|
+
student: studentLabel ?? null,
|
|
1076
|
+
today,
|
|
1077
|
+
tomorrow,
|
|
1078
|
+
todaySchedule,
|
|
1079
|
+
tomorrowSchedule,
|
|
1080
|
+
upcomingExams,
|
|
1081
|
+
recentHomework,
|
|
1082
|
+
recentNews,
|
|
1083
|
+
recentMessages,
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
729
1086
|
async function outputMessages(client, opts) {
|
|
730
1087
|
const messages = await client.messages.list(opts.folder);
|
|
731
1088
|
const slice = messages.slice(0, opts.limit);
|
|
@@ -909,6 +1266,10 @@ async function resolveStudentForFlags(profile, config, student) {
|
|
|
909
1266
|
const exact = students.find((s) => s.studentNumber === student);
|
|
910
1267
|
if (exact)
|
|
911
1268
|
return exact;
|
|
1269
|
+
const needle = student.toLowerCase();
|
|
1270
|
+
const substring = students.find((s) => s.name.toLowerCase().includes(needle));
|
|
1271
|
+
if (substring)
|
|
1272
|
+
return substring;
|
|
912
1273
|
const match = students.find((s) => fuzzyIncludes(s.name, student));
|
|
913
1274
|
if (match)
|
|
914
1275
|
return match;
|
|
@@ -976,8 +1337,8 @@ async function outputAllExams(profile, config, limit, json) {
|
|
|
976
1337
|
const results = [];
|
|
977
1338
|
for (const student of students) {
|
|
978
1339
|
const client = await WilmaClient.login({ ...profile, studentNumber: student.studentNumber });
|
|
979
|
-
const
|
|
980
|
-
results.push({ student, items:
|
|
1340
|
+
const overview = await client.overview.get();
|
|
1341
|
+
results.push({ student, items: overview.upcomingExams.slice(0, limit) });
|
|
981
1342
|
}
|
|
982
1343
|
if (json) {
|
|
983
1344
|
console.log(JSON.stringify({ students: results }, null, 2));
|
|
@@ -986,11 +1347,101 @@ async function outputAllExams(profile, config, limit, json) {
|
|
|
986
1347
|
results.forEach((entry) => {
|
|
987
1348
|
console.log(`\n[${entry.student.name}]`);
|
|
988
1349
|
entry.items.forEach((exam) => {
|
|
989
|
-
const
|
|
990
|
-
console.log(`- ${date} ${exam.subject}`);
|
|
1350
|
+
const topic = exam.topic ? ` — ${compactText(exam.topic)}` : "";
|
|
1351
|
+
console.log(`- ${exam.date} ${exam.subject}: ${exam.name}${topic}`);
|
|
991
1352
|
});
|
|
992
1353
|
});
|
|
993
1354
|
}
|
|
1355
|
+
async function outputAllOverviewCommand(profile, config, command, flags) {
|
|
1356
|
+
const students = await getStudentsForCommand(profile, config);
|
|
1357
|
+
if (command === "summary") {
|
|
1358
|
+
if (flags.json) {
|
|
1359
|
+
const summaries = [];
|
|
1360
|
+
for (const student of students) {
|
|
1361
|
+
const client = await WilmaClient.login({ ...profile, studentNumber: student.studentNumber });
|
|
1362
|
+
const [overview, news, messages] = await Promise.all([
|
|
1363
|
+
client.overview.get(),
|
|
1364
|
+
client.news.list(),
|
|
1365
|
+
client.messages.list("inbox"),
|
|
1366
|
+
]);
|
|
1367
|
+
summaries.push({
|
|
1368
|
+
student,
|
|
1369
|
+
summary: buildSummaryData(overview, news, messages, flags.days ?? 7, student.name),
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
console.log(JSON.stringify({
|
|
1373
|
+
generatedAt: new Date().toISOString(),
|
|
1374
|
+
students: summaries,
|
|
1375
|
+
}, null, 2));
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
// Human-readable summary output per student
|
|
1379
|
+
for (const student of students) {
|
|
1380
|
+
const client = await WilmaClient.login({ ...profile, studentNumber: student.studentNumber });
|
|
1381
|
+
await outputSummary(client, {
|
|
1382
|
+
days: flags.days ?? 7,
|
|
1383
|
+
json: false,
|
|
1384
|
+
label: student.name,
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
const results = [];
|
|
1390
|
+
for (const student of students) {
|
|
1391
|
+
const client = await WilmaClient.login({ ...profile, studentNumber: student.studentNumber });
|
|
1392
|
+
const overview = await client.overview.get();
|
|
1393
|
+
if (command === "schedule") {
|
|
1394
|
+
const when = flags.when || "week";
|
|
1395
|
+
let startDate, endDate;
|
|
1396
|
+
if (when === "today") {
|
|
1397
|
+
startDate = endDate = todayString();
|
|
1398
|
+
}
|
|
1399
|
+
else if (when === "tomorrow") {
|
|
1400
|
+
startDate = endDate = nextSchoolDay();
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
[startDate, endDate] = currentWeekBounds();
|
|
1404
|
+
}
|
|
1405
|
+
results.push({
|
|
1406
|
+
student,
|
|
1407
|
+
data: overview.schedule.filter((l) => l.date >= startDate && l.date <= endDate),
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
else if (command === "homework") {
|
|
1411
|
+
results.push({ student, data: overview.homework.slice(0, flags.limit ?? 10) });
|
|
1412
|
+
}
|
|
1413
|
+
else if (command === "grades") {
|
|
1414
|
+
results.push({ student, data: overview.grades.slice(0, flags.limit ?? 20) });
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (flags.json) {
|
|
1418
|
+
console.log(JSON.stringify({ students: results.map((r) => ({ student: r.student, items: r.data })) }, null, 2));
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
for (const r of results) {
|
|
1422
|
+
console.log(`\n[${r.student.name}]`);
|
|
1423
|
+
const items = r.data;
|
|
1424
|
+
if (!items.length) {
|
|
1425
|
+
console.log(" (none)");
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
if (command === "schedule") {
|
|
1429
|
+
for (const l of items) {
|
|
1430
|
+
console.log(` ${l.date} ${l.start}-${l.end} ${l.subject} - ${l.teacher}`);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
else if (command === "homework") {
|
|
1434
|
+
for (const hw of items) {
|
|
1435
|
+
console.log(` ${hw.date} ${hw.subject}: ${compactText(hw.homework)}`);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
else if (command === "grades") {
|
|
1439
|
+
for (const g of items) {
|
|
1440
|
+
console.log(` ${g.date} ${g.subject}: ${g.name} — ${g.grade}`);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
994
1445
|
async function printStudentSelectionHelp(profile, config) {
|
|
995
1446
|
const students = await getStudentsForCommand(profile, config);
|
|
996
1447
|
console.error("Multiple students found. Use --student <id|name> or --all-students.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wilm-ai/wilma-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@inquirer/prompts": "^5.3.8",
|
|
13
|
-
"@wilm-ai/wilma-client": "^
|
|
13
|
+
"@wilm-ai/wilma-client": "^1.1.0"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"typescript": "^5.6.3"
|