@wilm-ai/wilma-cli 2.3.0 → 2.3.2
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 +1 -1
- package/dist/agent-data.d.ts +2 -0
- package/dist/agent-data.js +13 -8
- package/dist/cli-help.js +1 -1
- package/dist/cli-output.js +5 -4
- package/dist/login-server.js +2 -2
- package/dist/mcp-tools.d.ts +1 -1
- package/dist/mcp-tools.js +4 -4
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @wilm-ai/wilma-cli
|
|
2
2
|
|
|
3
|
-
Command line interface for Wilma (Finnish school system), built for parents and AI agents.
|
|
3
|
+
Command line interface for Wilma (Finnish school system), built for parents and AI agents. Students can use it with their own Wilma login too.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
Needs Node.js 20.18.1 or newer.
|
package/dist/agent-data.d.ts
CHANGED
|
@@ -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;
|
package/dist/agent-data.js
CHANGED
|
@@ -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
|
|
191
|
-
*
|
|
192
|
-
*
|
|
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.
|
|
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
|
-
|
|
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));
|
package/dist/cli-help.js
CHANGED
|
@@ -63,7 +63,7 @@ const COMMAND_HELP = {
|
|
|
63
63
|
},
|
|
64
64
|
login: {
|
|
65
65
|
usage: ["wilma login [--no-browser]", "wilma login --tenant <url|city> --username <name> [--password-stdin] [--totp-secret <key>]"],
|
|
66
|
-
about: "Log in to Wilma. Opens a one-time login page in the browser where the
|
|
66
|
+
about: "Log in to Wilma, with a parent's account or a student's own. Opens a one-time login page in the browser where the user picks their school's Wilma; logins are saved on this computer. Families with children on different Wilmas add each one there. Without a browser, pass --tenant and --username with the password on stdin or in WILMA_PASSWORD.",
|
|
67
67
|
examples: ["wilma login"],
|
|
68
68
|
},
|
|
69
69
|
accounts: {
|
package/dist/cli-output.js
CHANGED
|
@@ -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
|
-
|
|
173
|
-
|
|
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/login-server.js
CHANGED
|
@@ -260,7 +260,7 @@ export function renderLoginPage(opts) {
|
|
|
260
260
|
submit: "Log in",
|
|
261
261
|
working: "Checking with Wilma…",
|
|
262
262
|
doneTitle: "You're connected",
|
|
263
|
-
doneIntro: "
|
|
263
|
+
doneIntro: "Found on this login:",
|
|
264
264
|
addAnother: "Add another Wilma",
|
|
265
265
|
addAnotherHint: "If one of your children goes to a school with a different Wilma, add that login too.",
|
|
266
266
|
finish: "Done",
|
|
@@ -304,7 +304,7 @@ export function renderLoginPage(opts) {
|
|
|
304
304
|
submit: "Kirjaudu",
|
|
305
305
|
working: "Tarkistetaan Wilmasta…",
|
|
306
306
|
doneTitle: "Yhteys on valmis",
|
|
307
|
-
doneIntro: "
|
|
307
|
+
doneIntro: "Näillä tunnuksilla löytyi:",
|
|
308
308
|
addAnother: "Lisää toinen Wilma",
|
|
309
309
|
addAnotherHint: "Jos joku lapsistasi käy koulua, jolla on eri Wilma, lisää myös sen tunnukset.",
|
|
310
310
|
finish: "Valmis",
|
package/dist/mcp-tools.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
3
|
import type { FetchedAttachment, WilmaAccess } from "./agent-data.js";
|
|
4
|
-
export declare const INSTRUCTIONS = "Read-only access to Finland's Wilma school system for a parent/guardian.\n- Start with wilma_summary: today's and tomorrow's lessons, upcoming exams, recent homework, news and messages for every child in one call.\n- Tools cover all children by default; pass \"student\" (name or student number) to narrow to one child.\n- Wilma content is usually in Finnish. Answer in the user's language and translate as needed. Write dates the way that language does: Finnish \"ke 7.10.\" (with the final period), English \"Wed 7 Oct\".\n- Message and news lists return ids; use wilma_read_message / wilma_read_news for full text. Bulletins can link attachments; fetch them with wilma_get_news_attachment.\n- Teachers' feedback (\"forgot books\", \"did well\") and absences are lesson notes: wilma_lesson_notes for recent days, wilma_lesson_notes_summary for counts. Course and report-card grades are in wilma_gradebook.\n- If a tool says the user isn't logged in, relay the login link or instructions to the user exactly; never ask the user to type their Wilma password into the chat.\n- Wilma allows one session per account, so each WilmAI login logs the user out of Wilma in their browser. If they want to use Wilma in the browser, call wilma_logout and then call Wilma tools only when they ask again.\n- If a result ends with an \"Update note\", answer the question first, then pass the note on in one sentence.";
|
|
4
|
+
export declare const INSTRUCTIONS = "Read-only access to Finland's Wilma school system for a parent/guardian, or for a student with their own login.\n- Start with wilma_summary: today's and tomorrow's lessons, upcoming exams, recent homework, news and messages for every child in one call.\n- Tools cover all children by default (a student's own login has just that student); pass \"student\" (name or student number) to narrow to one child.\n- Wilma content is usually in Finnish. Answer in the user's language and translate as needed. Write dates the way that language does: Finnish \"ke 7.10.\" (with the final period), English \"Wed 7 Oct\".\n- Message and news lists return ids; use wilma_read_message / wilma_read_news for full text. Bulletins can link attachments; fetch them with wilma_get_news_attachment.\n- Teachers' feedback (\"forgot books\", \"did well\") and absences are lesson notes: wilma_lesson_notes for recent days, wilma_lesson_notes_summary for counts. Course and report-card grades are in wilma_gradebook.\n- If a tool says the user isn't logged in, relay the login link or instructions to the user exactly; never ask the user to type their Wilma password into the chat.\n- Wilma allows one session per account, so each WilmAI login logs the user out of Wilma in their browser. If they want to use Wilma in the browser, call wilma_logout and then call Wilma tools only when they ask again.\n- If a result ends with an \"Update note\", answer the question first, then pass the note on in one sentence.";
|
|
5
5
|
export declare const READ_ONLY: {
|
|
6
6
|
readonly readOnlyHint: true;
|
|
7
7
|
readonly destructiveHint: false;
|
package/dist/mcp-tools.js
CHANGED
|
@@ -5,9 +5,9 @@ import { toAgentJson } from "./output-json.js";
|
|
|
5
5
|
* Wilma data tools for the local stdio server (`wilma mcp`) and any other
|
|
6
6
|
* host. Each host decides how a tool call gets a WilmaAccess.
|
|
7
7
|
*/
|
|
8
|
-
export const INSTRUCTIONS = `Read-only access to Finland's Wilma school system for a parent/guardian.
|
|
8
|
+
export const INSTRUCTIONS = `Read-only access to Finland's Wilma school system for a parent/guardian, or for a student with their own login.
|
|
9
9
|
- Start with wilma_summary: today's and tomorrow's lessons, upcoming exams, recent homework, news and messages for every child in one call.
|
|
10
|
-
- Tools cover all children by default; pass "student" (name or student number) to narrow to one child.
|
|
10
|
+
- Tools cover all children by default (a student's own login has just that student); pass "student" (name or student number) to narrow to one child.
|
|
11
11
|
- Wilma content is usually in Finnish. Answer in the user's language and translate as needed. Write dates the way that language does: Finnish "ke 7.10." (with the final period), English "Wed 7 Oct".
|
|
12
12
|
- Message and news lists return ids; use wilma_read_message / wilma_read_news for full text. Bulletins can link attachments; fetch them with wilma_get_news_attachment.
|
|
13
13
|
- Teachers' feedback ("forgot books", "did well") and absences are lesson notes: wilma_lesson_notes for recent days, wilma_lesson_notes_summary for counts. Course and report-card grades are in wilma_gradebook.
|
|
@@ -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,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wilm-ai/wilma-cli",
|
|
3
|
-
"version": "2.3.
|
|
4
|
-
"description": "Read your children's Wilma (Finnish school system) from the terminal or an AI assistant: schedule, homework, exams, messages and news.",
|
|
3
|
+
"version": "2.3.2",
|
|
4
|
+
"description": "Read your children's Wilma (Finnish school system) from the terminal or an AI assistant: schedule, homework, exams, messages and news. Students can use their own login too.",
|
|
5
|
+
"mcpName": "ai.wilm/wilma",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"scripts": {
|
|
22
23
|
"build": "tsc -p tsconfig.json",
|
|
23
24
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
24
|
-
"test": "pnpm --filter @wilm-ai/wilma-client build && pnpm run build && node test/news-resources.mjs && node test/mcp-login.mjs && node test/mfa.mjs && node test/logout.mjs && node test/shared-session.mjs && node test/audit.mjs && node test/update-notice.mjs",
|
|
25
|
+
"test": "pnpm --filter @wilm-ai/wilma-client build && pnpm run build && node test/news-resources.mjs && node test/mcp-login.mjs && node test/mfa.mjs && node test/logout.mjs && node test/shared-session.mjs && node test/audit.mjs && node test/update-notice.mjs && node test/registry.mjs",
|
|
25
26
|
"start": "node dist/index.js",
|
|
26
27
|
"prepack": "pnpm --filter @wilm-ai/wilma-client build && pnpm --filter @wilm-ai/wilma-cli build",
|
|
27
28
|
"test:live": "tsc -p tsconfig.json && node test/live.spec.mjs",
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@inquirer/prompts": "^5.3.8",
|
|
32
33
|
"@modelcontextprotocol/sdk": "^1.32.0",
|
|
33
|
-
"@wilm-ai/wilma-client": "^1.
|
|
34
|
+
"@wilm-ai/wilma-client": "^1.8.0",
|
|
34
35
|
"zod": "^4.6.6"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|