@wilm-ai/wilma-cli 2.3.0 → 2.3.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.
@@ -479,6 +479,7 @@ export declare class WilmaAccess {
479
479
  students: ({
480
480
  student: StudentRef;
481
481
  } & {
482
+ unavailable?: string[] | undefined;
482
483
  news: NewsItem[];
483
484
  })[];
484
485
  problems: {
@@ -489,6 +490,7 @@ export declare class WilmaAccess {
489
490
  students: ({
490
491
  student: StudentRef;
491
492
  } & {
493
+ unavailable?: string[] | undefined;
492
494
  news: NewsItem[];
493
495
  })[];
494
496
  problems?: undefined;
@@ -187,9 +187,10 @@ function selectNews(news, opts = {}) {
187
187
  return [...dated, ...pinnedUndated, ...older];
188
188
  }
189
189
  /**
190
- * What a daily summary needs, fetched at once. The front page, bulletins and
191
- * inbox are required; meeting invitations, lesson notes and exam times are
192
- * extras — if Wilma can't give one, the summary says so in `unavailable`.
190
+ * What a daily summary needs, fetched at once. The front page and inbox are
191
+ * required; bulletins (some Wilmas don't show them to a guardian), meeting
192
+ * invitations, lesson notes and exam times aren't — if Wilma can't give one,
193
+ * the summary says so in `unavailable`.
193
194
  */
194
195
  async function fetchSummaryInputs(client, opts) {
195
196
  const unavailable = [];
@@ -202,14 +203,16 @@ async function fetchSummaryInputs(client, opts) {
202
203
  const [overview, calendar, news, inbox, appointments, lessonNotes] = await Promise.all([
203
204
  client.overview.get(),
204
205
  client.exams.calendarOrEmpty(),
205
- client.news.list(),
206
+ client.news.listIfShown(),
206
207
  client.messages.list("inbox"),
207
208
  optional("appointments", client.messages.list("appointments"), []),
208
209
  optional("lessonNotes", client.attendance.list({ from: opts.notesFrom, to: todayString() }), []),
209
210
  ]);
211
+ if (news === null)
212
+ unavailable.push("news");
210
213
  return {
211
214
  overview: { ...overview, upcomingExams: addExamTimes(overview.upcomingExams, calendar) },
212
- news,
215
+ news: news ?? [],
213
216
  messages: [...inbox, ...appointments],
214
217
  lessonNotes,
215
218
  unavailable,
@@ -591,9 +594,11 @@ export class WilmaAccess {
591
594
  return { student, message: item };
592
595
  }
593
596
  async news(opts = {}) {
594
- return this.perStudent(opts.student, async (client) => ({
595
- news: selectNews(await client.news.list(), { limit: opts.limit, includeOlder: opts.includeOlder }),
596
- }));
597
+ return this.perStudent(opts.student, async (client) => {
598
+ const list = await client.news.listIfShown();
599
+ // A Wilma that doesn't show this account its bulletins: none, and it says so (not the same as "no bulletins").
600
+ return { news: selectNews(list ?? [], { limit: opts.limit, includeOlder: opts.includeOlder }), ...(list === null ? { unavailable: ["news"] } : {}) };
601
+ });
597
602
  }
598
603
  async newsItem(opts) {
599
604
  const { student, item } = await this.firstStudentWith(opts.student, (client) => client.news.get(opts.id));
@@ -38,7 +38,7 @@ function perStudent(result, key, render) {
38
38
  const several = result.students.length > 1;
39
39
  const lines = [];
40
40
  for (const entry of result.students) {
41
- lines.push(...heading(entry.student, several), ...render(entry[key], entry.student));
41
+ lines.push(...heading(entry.student, several), ...render(entry[key], entry.student, entry));
42
42
  }
43
43
  for (const problem of result.problems ?? [])
44
44
  lines.push("", `Couldn't read ${problem.wilma}: ${problem.message}`);
@@ -168,9 +168,10 @@ export function formatText(command, action, result) {
168
168
  : []),
169
169
  ].join("\n");
170
170
  }
171
- return perStudent(result, "news", (items) => {
172
- const lines = items.length ? items.map((n) => ` ${newsLine(n)}`) : [" No bulletins."];
173
- return lines;
171
+ return perStudent(result, "news", (items, _student, entry) => {
172
+ if (entry.unavailable?.includes("news"))
173
+ return [" This Wilma doesn't show bulletins to this account."];
174
+ return items.length ? items.map((n) => ` ${newsLine(n)}`) : [" No bulletins."];
174
175
  });
175
176
  }
176
177
  case "printouts":
package/dist/mcp-tools.js CHANGED
@@ -74,7 +74,7 @@ function attachmentResult(fetched, savedPath) {
74
74
  export function registerWilmaTools(server, ctx) {
75
75
  server.registerTool("wilma_summary", {
76
76
  title: "School summary",
77
- description: "Daily briefing per child: today's and the next school day's lessons, upcoming exams (with start times when given), recent homework, lesson notes since the previous school day (teachers' feedback and absences), recent bulletins, and recent and unread messages (ids, unread, replyCount). The best starting point for any question about school.",
77
+ description: "Daily briefing per child: today's and the next school day's lessons, upcoming exams (with start times when given), recent homework, lesson notes since the previous school day (teachers' feedback and absences), recent bulletins, and recent and unread messages (ids, unread, replyCount). What Wilma didn't give is listed in unavailable (\"news\": this Wilma doesn't show this account its bulletins). The best starting point for any question about school.",
78
78
  inputSchema: {
79
79
  student: studentArg,
80
80
  days: z.number().int().min(1).max(60).optional().describe("How many days back to include news and messages (default 7)."),
@@ -191,7 +191,7 @@ export function registerWilmaTools(server, ctx) {
191
191
  }, async ({ id, student }) => ctx.withAccess(async (a) => json(await a.message({ id, student }))));
192
192
  server.registerTool("wilma_list_news", {
193
193
  title: "List school news",
194
- description: "School bulletins (tiedotteet): the newest dated ones, then pinned ones (pinned: true, e.g. the school-year bulletin), and with include_older also older bulletins (archived: true). Use wilma_read_news for the full text and attachments.",
194
+ description: "School bulletins (tiedotteet): the newest dated ones, then pinned ones (pinned: true, e.g. the school-year bulletin), and with include_older also older bulletins (archived: true). Use wilma_read_news for the full text and attachments. unavailable: [\"news\"] means this Wilma doesn't show this account its bulletins, which isn't the same as having none.",
195
195
  inputSchema: {
196
196
  student: studentArg,
197
197
  limit: z.number().int().min(1).max(100).optional().describe("How many dated bulletins (default 20). Pinned ones are always included."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wilm-ai/wilma-cli",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "Read your children's Wilma (Finnish school system) from the terminal or an AI assistant: schedule, homework, exams, messages and news.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "dependencies": {
31
31
  "@inquirer/prompts": "^5.3.8",
32
32
  "@modelcontextprotocol/sdk": "^1.32.0",
33
- "@wilm-ai/wilma-client": "^1.7.1",
33
+ "@wilm-ai/wilma-client": "^1.8.0",
34
34
  "zod": "^4.6.6"
35
35
  },
36
36
  "devDependencies": {