moodle-cli 0.7.0 → 0.7.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/moodle.js CHANGED
@@ -4555,7 +4555,7 @@ function escapeXml(value) {
4555
4555
  }
4556
4556
 
4557
4557
  // src/version.ts
4558
- var VERSION = "0.7.0";
4558
+ var VERSION = "0.7.1";
4559
4559
 
4560
4560
  // src/forum.ts
4561
4561
  function parseDiscussionReference(value) {
@@ -6073,6 +6073,7 @@ function asDeploymentError(error) {
6073
6073
  }
6074
6074
 
6075
6075
  // src/mcp/deployment/node-adapters.ts
6076
+ import { isDeepStrictEqual } from "util";
6076
6077
  import { spawn as spawn2 } from "child_process";
6077
6078
  import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
6078
6079
  import { chmod as chmod5, copyFile, mkdir as mkdir7, mkdtemp, readFile as readFile6, rm as rm6, writeFile as writeFile7 } from "fs/promises";
@@ -6910,6 +6911,19 @@ var FetchManagedWorkerClient = class {
6910
6911
  if (!moodleUser) {
6911
6912
  throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP get_user check returned no Moodle user");
6912
6913
  }
6914
+ const coursesResult = await this.mcpCall(input.endpoint, input.mcpAccessToken, "tools/call", { name: "list_courses", arguments: { limit: 1 } }, 4);
6915
+ const courses = readableToolResult(coursesResult).courses;
6916
+ if (!Array.isArray(courses)) throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP list_courses returned no course list");
6917
+ if (courses.length) {
6918
+ const first2 = courses[0];
6919
+ if (!isRecord8(first2) || !Number.isSafeInteger(first2.id) || Number(first2.id) <= 0) {
6920
+ throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP list_courses returned no usable course ID");
6921
+ }
6922
+ const detail = readableToolResult(await this.mcpCall(input.endpoint, input.mcpAccessToken, "tools/call", { name: "get_course", arguments: { courseId: first2.id } }, 5));
6923
+ if (!isRecord8(detail.course) || !isRecord8(detail.course.course) || detail.course.course.id !== first2.id || !Array.isArray(detail.course.sections)) {
6924
+ throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP course lookup did not match the listed course");
6925
+ }
6926
+ }
6913
6927
  return { moodleUser };
6914
6928
  }
6915
6929
  async manageClients(input) {
@@ -6970,6 +6984,7 @@ var FetchManagedWorkerClient = class {
6970
6984
  if (!response.ok || !isRecord8(body) || body.jsonrpc !== "2.0" || body.id !== id || "error" in body || !("result" in body)) {
6971
6985
  throw new DeploymentApplyError("MCP_SMOKE_FAILED", `MCP ${method} check failed`);
6972
6986
  }
6987
+ if (method === "tools/call") readableToolResult(body.result);
6973
6988
  return body.result;
6974
6989
  }
6975
6990
  async fetchWithRetry(input, init, retryable) {
@@ -7119,11 +7134,19 @@ function firstHealthCheck(body, name) {
7119
7134
  const check = checks[name][0];
7120
7135
  return isRecord8(check) ? check : null;
7121
7136
  }
7122
- function mcpUserFullname(result) {
7123
- if (!isRecord8(result) || !isRecord8(result.structuredContent)) {
7124
- return null;
7137
+ function readableToolResult(result) {
7138
+ if (isRecord8(result) && result.isError !== true && Array.isArray(result.content)) {
7139
+ try {
7140
+ const text = result.content.filter((block) => isRecord8(block) && block.type === "text").map((block) => block.text).join("\n");
7141
+ const parsed = JSON.parse(text);
7142
+ if (isRecord8(parsed) && isDeepStrictEqual(parsed, result.structuredContent)) return parsed;
7143
+ } catch {
7144
+ }
7125
7145
  }
7126
- const user = result.structuredContent.user;
7146
+ throw new DeploymentApplyError("MCP_CONTENT_INCOMPLETE", "MCP text content is missing the complete structured result");
7147
+ }
7148
+ function mcpUserFullname(result) {
7149
+ const user = readableToolResult(result).user;
7127
7150
  return isRecord8(user) && typeof user.fullname === "string" && user.fullname.trim() ? user.fullname.trim() : null;
7128
7151
  }
7129
7152
  function parseJsonOutput(output) {
@@ -7650,7 +7673,7 @@ var TOOL_REGISTRATIONS = [
7650
7673
  },
7651
7674
  {
7652
7675
  name: "get_overview",
7653
- description: "Get a bounded dashboard overview with courses, deadlines, and alerts.",
7676
+ description: "Get courses, deadlines and alerts. Course IDs can be used as courseId in other tools; todo due_at values are Unix timestamps in seconds.",
7654
7677
  input: z3.object({
7655
7678
  todoLimit: z3.number().int().min(1).max(100).optional().default(5),
7656
7679
  todoDays: z3.number().int().min(1).max(365).optional(),
@@ -7667,7 +7690,7 @@ var TOOL_REGISTRATIONS = [
7667
7690
  },
7668
7691
  {
7669
7692
  name: "list_courses",
7670
- description: "List the authenticated user's Moodle courses.",
7693
+ description: "List the authenticated user's Moodle courses with IDs and names. Use a returned id as courseId for get_course, list_activities, get_grades or list_forums.",
7671
7694
  input: z3.object({ limit: z3.number().int().min(1).max(200).optional().default(100) }).strict(),
7672
7695
  output: z3.object({ courses: z3.array(courseValue) })
7673
7696
  },
@@ -7867,7 +7890,7 @@ async function callTool(gateway, params) {
7867
7890
  const payload = await runGatewayTool(gateway, name, input);
7868
7891
  const structuredContent = registration.output.parse(wrapToolOutput(name, payload));
7869
7892
  return {
7870
- content: toolContent(name, payload),
7893
+ content: toolContent(name, payload, structuredContent),
7871
7894
  structuredContent,
7872
7895
  resultType: "complete",
7873
7896
  _meta: RESULT_META
@@ -7878,7 +7901,7 @@ async function callTool(gateway, params) {
7878
7901
  }
7879
7902
  const mapped = mapMoodleError(error);
7880
7903
  return {
7881
- content: [{ type: "text", text: mapped.message }],
7904
+ content: [{ type: "text", text: JSON.stringify({ error: mapped }) }],
7882
7905
  structuredContent: { error: mapped },
7883
7906
  isError: true,
7884
7907
  resultType: "complete",
@@ -7959,8 +7982,8 @@ function wrapToolOutput(name, payload) {
7959
7982
  }
7960
7983
  return { [keys[name] ?? "result"]: payload };
7961
7984
  }
7962
- function toolContent(name, payload) {
7963
- const text = { type: "text", text: summarizeToolOutput(name, payload) };
7985
+ function toolContent(name, payload, structuredContent) {
7986
+ const text = { type: "text", text: JSON.stringify(structuredContent) };
7964
7987
  if (name !== "get_file" || !isMoodleFile(payload)) return [text];
7965
7988
  return [
7966
7989
  text,
@@ -7974,42 +7997,6 @@ function toolContent(name, payload) {
7974
7997
  }
7975
7998
  ];
7976
7999
  }
7977
- function summarizeToolOutput(name, payload) {
7978
- if (Array.isArray(payload)) {
7979
- const labels = {
7980
- list_courses: ["Moodle course", "Moodle courses"],
7981
- list_activities: ["activity", "activities"],
7982
- list_forums: ["forum", "forums"],
7983
- search_forums: ["forum result", "forum results"]
7984
- };
7985
- const [singular, plural] = labels[name] ?? ["result", "results"];
7986
- return `Found ${payload.length} ${payload.length === 1 ? singular : plural}.`;
7987
- }
7988
- if (name === "get_user" && isRecord9(payload)) {
7989
- return `Authenticated as ${stringValue4(payload.fullname)}.`;
7990
- }
7991
- if (name === "get_overview" && isRecord9(payload)) {
7992
- const courses = Array.isArray(payload.courses) ? payload.courses.length : 0;
7993
- const todo = Array.isArray(payload.todo) ? payload.todo.length : 0;
7994
- return `Overview includes ${courses} courses and ${todo} upcoming items.`;
7995
- }
7996
- if (name === "get_course" && isRecord9(payload) && isRecord9(payload.course)) {
7997
- return `Loaded course ${stringValue4(payload.course.fullname) || numberValue3(payload.course.id)}.`;
7998
- }
7999
- if (name === "get_activity" && isRecord9(payload)) {
8000
- return `Loaded activity ${stringValue4(payload.name) || numberValue3(payload.id)}.`;
8001
- }
8002
- if (name === "get_grades" && isRecord9(payload)) {
8003
- return `Loaded grades for ${stringValue4(payload.course_name) || numberValue3(payload.course_id)}.`;
8004
- }
8005
- if (name === "get_thread" && isRecord9(payload)) {
8006
- return `Loaded forum thread ${stringValue4(payload.subject) || numberValue3(payload.id)}.`;
8007
- }
8008
- if (name === "get_file" && isMoodleFile(payload)) {
8009
- return `Loaded Moodle file ${payload.name} (${payload.bytes} bytes).`;
8010
- }
8011
- return "Moodle request completed.";
8012
- }
8013
8000
  function mapMoodleError(error) {
8014
8001
  const record = isRecord9(error) ? error : {};
8015
8002
  const code = typeof record.code === "string" ? record.code : "";
@@ -26452,7 +26452,7 @@ function stringValue(value) {
26452
26452
  }
26453
26453
 
26454
26454
  // src/version.ts
26455
- var VERSION = "0.7.0";
26455
+ var VERSION = "0.7.1";
26456
26456
 
26457
26457
  // src/worker/http.ts
26458
26458
  var HEALTH_PATH = "/healthz";
@@ -29264,7 +29264,7 @@ var TOOL_REGISTRATIONS = [
29264
29264
  },
29265
29265
  {
29266
29266
  name: "get_overview",
29267
- description: "Get a bounded dashboard overview with courses, deadlines, and alerts.",
29267
+ description: "Get courses, deadlines and alerts. Course IDs can be used as courseId in other tools; todo due_at values are Unix timestamps in seconds.",
29268
29268
  input: external_exports.object({
29269
29269
  todoLimit: external_exports.number().int().min(1).max(100).optional().default(5),
29270
29270
  todoDays: external_exports.number().int().min(1).max(365).optional(),
@@ -29281,7 +29281,7 @@ var TOOL_REGISTRATIONS = [
29281
29281
  },
29282
29282
  {
29283
29283
  name: "list_courses",
29284
- description: "List the authenticated user's Moodle courses.",
29284
+ description: "List the authenticated user's Moodle courses with IDs and names. Use a returned id as courseId for get_course, list_activities, get_grades or list_forums.",
29285
29285
  input: external_exports.object({ limit: external_exports.number().int().min(1).max(200).optional().default(100) }).strict(),
29286
29286
  output: external_exports.object({ courses: external_exports.array(courseValue) })
29287
29287
  },
@@ -29481,7 +29481,7 @@ async function callTool(gateway, params) {
29481
29481
  const payload = await runGatewayTool(gateway, name, input2);
29482
29482
  const structuredContent = registration.output.parse(wrapToolOutput(name, payload));
29483
29483
  return {
29484
- content: toolContent(name, payload),
29484
+ content: toolContent(name, payload, structuredContent),
29485
29485
  structuredContent,
29486
29486
  resultType: "complete",
29487
29487
  _meta: RESULT_META
@@ -29492,7 +29492,7 @@ async function callTool(gateway, params) {
29492
29492
  }
29493
29493
  const mapped = mapMoodleError(error62);
29494
29494
  return {
29495
- content: [{ type: "text", text: mapped.message }],
29495
+ content: [{ type: "text", text: JSON.stringify({ error: mapped }) }],
29496
29496
  structuredContent: { error: mapped },
29497
29497
  isError: true,
29498
29498
  resultType: "complete",
@@ -29573,8 +29573,8 @@ function wrapToolOutput(name, payload) {
29573
29573
  }
29574
29574
  return { [keys[name] ?? "result"]: payload };
29575
29575
  }
29576
- function toolContent(name, payload) {
29577
- const text = { type: "text", text: summarizeToolOutput(name, payload) };
29576
+ function toolContent(name, payload, structuredContent) {
29577
+ const text = { type: "text", text: JSON.stringify(structuredContent) };
29578
29578
  if (name !== "get_file" || !isMoodleFile(payload)) return [text];
29579
29579
  return [
29580
29580
  text,
@@ -29588,42 +29588,6 @@ function toolContent(name, payload) {
29588
29588
  }
29589
29589
  ];
29590
29590
  }
29591
- function summarizeToolOutput(name, payload) {
29592
- if (Array.isArray(payload)) {
29593
- const labels = {
29594
- list_courses: ["Moodle course", "Moodle courses"],
29595
- list_activities: ["activity", "activities"],
29596
- list_forums: ["forum", "forums"],
29597
- search_forums: ["forum result", "forum results"]
29598
- };
29599
- const [singular, plural2] = labels[name] ?? ["result", "results"];
29600
- return `Found ${payload.length} ${payload.length === 1 ? singular : plural2}.`;
29601
- }
29602
- if (name === "get_user" && isRecord7(payload)) {
29603
- return `Authenticated as ${stringValue4(payload.fullname)}.`;
29604
- }
29605
- if (name === "get_overview" && isRecord7(payload)) {
29606
- const courses = Array.isArray(payload.courses) ? payload.courses.length : 0;
29607
- const todo = Array.isArray(payload.todo) ? payload.todo.length : 0;
29608
- return `Overview includes ${courses} courses and ${todo} upcoming items.`;
29609
- }
29610
- if (name === "get_course" && isRecord7(payload) && isRecord7(payload.course)) {
29611
- return `Loaded course ${stringValue4(payload.course.fullname) || numberValue3(payload.course.id)}.`;
29612
- }
29613
- if (name === "get_activity" && isRecord7(payload)) {
29614
- return `Loaded activity ${stringValue4(payload.name) || numberValue3(payload.id)}.`;
29615
- }
29616
- if (name === "get_grades" && isRecord7(payload)) {
29617
- return `Loaded grades for ${stringValue4(payload.course_name) || numberValue3(payload.course_id)}.`;
29618
- }
29619
- if (name === "get_thread" && isRecord7(payload)) {
29620
- return `Loaded forum thread ${stringValue4(payload.subject) || numberValue3(payload.id)}.`;
29621
- }
29622
- if (name === "get_file" && isMoodleFile(payload)) {
29623
- return `Loaded Moodle file ${payload.name} (${payload.bytes} bytes).`;
29624
- }
29625
- return "Moodle request completed.";
29626
- }
29627
29591
  function mapMoodleError(error62) {
29628
29592
  const record2 = isRecord7(error62) ? error62 : {};
29629
29593
  const code = typeof record2.code === "string" ? record2.code : "";
@@ -26452,7 +26452,7 @@ function stringValue(value) {
26452
26452
  }
26453
26453
 
26454
26454
  // src/version.ts
26455
- var VERSION = "0.7.0";
26455
+ var VERSION = "0.7.1";
26456
26456
 
26457
26457
  // src/worker/http.ts
26458
26458
  var HEALTH_PATH = "/healthz";
@@ -29264,7 +29264,7 @@ var TOOL_REGISTRATIONS = [
29264
29264
  },
29265
29265
  {
29266
29266
  name: "get_overview",
29267
- description: "Get a bounded dashboard overview with courses, deadlines, and alerts.",
29267
+ description: "Get courses, deadlines and alerts. Course IDs can be used as courseId in other tools; todo due_at values are Unix timestamps in seconds.",
29268
29268
  input: external_exports.object({
29269
29269
  todoLimit: external_exports.number().int().min(1).max(100).optional().default(5),
29270
29270
  todoDays: external_exports.number().int().min(1).max(365).optional(),
@@ -29281,7 +29281,7 @@ var TOOL_REGISTRATIONS = [
29281
29281
  },
29282
29282
  {
29283
29283
  name: "list_courses",
29284
- description: "List the authenticated user's Moodle courses.",
29284
+ description: "List the authenticated user's Moodle courses with IDs and names. Use a returned id as courseId for get_course, list_activities, get_grades or list_forums.",
29285
29285
  input: external_exports.object({ limit: external_exports.number().int().min(1).max(200).optional().default(100) }).strict(),
29286
29286
  output: external_exports.object({ courses: external_exports.array(courseValue) })
29287
29287
  },
@@ -29481,7 +29481,7 @@ async function callTool(gateway, params) {
29481
29481
  const payload = await runGatewayTool(gateway, name, input2);
29482
29482
  const structuredContent = registration.output.parse(wrapToolOutput(name, payload));
29483
29483
  return {
29484
- content: toolContent(name, payload),
29484
+ content: toolContent(name, payload, structuredContent),
29485
29485
  structuredContent,
29486
29486
  resultType: "complete",
29487
29487
  _meta: RESULT_META
@@ -29492,7 +29492,7 @@ async function callTool(gateway, params) {
29492
29492
  }
29493
29493
  const mapped = mapMoodleError(error62);
29494
29494
  return {
29495
- content: [{ type: "text", text: mapped.message }],
29495
+ content: [{ type: "text", text: JSON.stringify({ error: mapped }) }],
29496
29496
  structuredContent: { error: mapped },
29497
29497
  isError: true,
29498
29498
  resultType: "complete",
@@ -29573,8 +29573,8 @@ function wrapToolOutput(name, payload) {
29573
29573
  }
29574
29574
  return { [keys[name] ?? "result"]: payload };
29575
29575
  }
29576
- function toolContent(name, payload) {
29577
- const text = { type: "text", text: summarizeToolOutput(name, payload) };
29576
+ function toolContent(name, payload, structuredContent) {
29577
+ const text = { type: "text", text: JSON.stringify(structuredContent) };
29578
29578
  if (name !== "get_file" || !isMoodleFile(payload)) return [text];
29579
29579
  return [
29580
29580
  text,
@@ -29588,42 +29588,6 @@ function toolContent(name, payload) {
29588
29588
  }
29589
29589
  ];
29590
29590
  }
29591
- function summarizeToolOutput(name, payload) {
29592
- if (Array.isArray(payload)) {
29593
- const labels = {
29594
- list_courses: ["Moodle course", "Moodle courses"],
29595
- list_activities: ["activity", "activities"],
29596
- list_forums: ["forum", "forums"],
29597
- search_forums: ["forum result", "forum results"]
29598
- };
29599
- const [singular, plural2] = labels[name] ?? ["result", "results"];
29600
- return `Found ${payload.length} ${payload.length === 1 ? singular : plural2}.`;
29601
- }
29602
- if (name === "get_user" && isRecord7(payload)) {
29603
- return `Authenticated as ${stringValue4(payload.fullname)}.`;
29604
- }
29605
- if (name === "get_overview" && isRecord7(payload)) {
29606
- const courses = Array.isArray(payload.courses) ? payload.courses.length : 0;
29607
- const todo = Array.isArray(payload.todo) ? payload.todo.length : 0;
29608
- return `Overview includes ${courses} courses and ${todo} upcoming items.`;
29609
- }
29610
- if (name === "get_course" && isRecord7(payload) && isRecord7(payload.course)) {
29611
- return `Loaded course ${stringValue4(payload.course.fullname) || numberValue3(payload.course.id)}.`;
29612
- }
29613
- if (name === "get_activity" && isRecord7(payload)) {
29614
- return `Loaded activity ${stringValue4(payload.name) || numberValue3(payload.id)}.`;
29615
- }
29616
- if (name === "get_grades" && isRecord7(payload)) {
29617
- return `Loaded grades for ${stringValue4(payload.course_name) || numberValue3(payload.course_id)}.`;
29618
- }
29619
- if (name === "get_thread" && isRecord7(payload)) {
29620
- return `Loaded forum thread ${stringValue4(payload.subject) || numberValue3(payload.id)}.`;
29621
- }
29622
- if (name === "get_file" && isMoodleFile(payload)) {
29623
- return `Loaded Moodle file ${payload.name} (${payload.bytes} bytes).`;
29624
- }
29625
- return "Moodle request completed.";
29626
- }
29627
29591
  function mapMoodleError(error62) {
29628
29592
  const record2 = isRecord7(error62) ? error62 : {};
29629
29593
  const code = typeof record2.code === "string" ? record2.code : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moodle-cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Terminal-first CLI for Moodle LMS",
5
5
  "license": "MIT",
6
6
  "type": "module",