moodle-cli 0.9.1 → 0.9.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/dist/moodle.js +69 -56
- package/dist/worker/recovery.js +1 -1
- package/dist/worker/worker.js +1 -1
- package/package.json +1 -1
package/dist/moodle.js
CHANGED
|
@@ -3719,7 +3719,7 @@ import {
|
|
|
3719
3719
|
colorEnabled as colorEnabled2,
|
|
3720
3720
|
confirm,
|
|
3721
3721
|
createProgram,
|
|
3722
|
-
createTheme as
|
|
3722
|
+
createTheme as createTheme5,
|
|
3723
3723
|
createUi as createUi3,
|
|
3724
3724
|
detectAudience,
|
|
3725
3725
|
examples,
|
|
@@ -7156,7 +7156,7 @@ async function readAll(input2) {
|
|
|
7156
7156
|
}
|
|
7157
7157
|
|
|
7158
7158
|
// src/version.ts
|
|
7159
|
-
var VERSION = "0.9.
|
|
7159
|
+
var VERSION = "0.9.2";
|
|
7160
7160
|
|
|
7161
7161
|
// src/forum.ts
|
|
7162
7162
|
function parseDiscussionReference(value) {
|
|
@@ -7310,6 +7310,7 @@ function resolveTopLevelUrl(baseUrlOrOptions, targetValue, resolveCourseIdForUrl
|
|
|
7310
7310
|
}
|
|
7311
7311
|
|
|
7312
7312
|
// src/mcp/cli.ts
|
|
7313
|
+
import { createTheme as createTheme4 } from "@bunizao/cli-kit";
|
|
7313
7314
|
import { createHash as createHash3 } from "crypto";
|
|
7314
7315
|
import { readFile as readFile9 } from "fs/promises";
|
|
7315
7316
|
import { homedir as homedir14 } from "os";
|
|
@@ -7934,6 +7935,8 @@ function isMissing3(error) {
|
|
|
7934
7935
|
}
|
|
7935
7936
|
|
|
7936
7937
|
// src/mcp/deployment/onboarding.ts
|
|
7938
|
+
import { createTheme as createTheme3 } from "@bunizao/cli-kit";
|
|
7939
|
+
var PLAIN = createTheme3(false);
|
|
7937
7940
|
var ONBOARDING_STAGES = [
|
|
7938
7941
|
{ id: "validate_moodle_session", index: 1, label: "Validating Moodle session" },
|
|
7939
7942
|
{ id: "check_cloudflare_access", index: 2, label: "Checking Cloudflare access" },
|
|
@@ -8025,39 +8028,41 @@ var ONBOARDING_COPY = {
|
|
|
8025
8028
|
"Wrangler will request Cloudflare authorization again."
|
|
8026
8029
|
].join("\n")
|
|
8027
8030
|
};
|
|
8028
|
-
function formatOnboardingStage(stageId, status) {
|
|
8031
|
+
function formatOnboardingStage(stageId, status, theme = PLAIN) {
|
|
8029
8032
|
const stage = ONBOARDING_STAGES.find((item) => item.id === stageId);
|
|
8030
8033
|
if (!stage) {
|
|
8031
8034
|
throw new Error(`Unknown onboarding stage: ${stageId}`);
|
|
8032
8035
|
}
|
|
8033
8036
|
const line = `[${stage.index}/8] ${stage.label}`;
|
|
8034
|
-
return status === "completed" ?
|
|
8037
|
+
return status === "completed" ? `${theme.tone("success", "\u2713")} ${line}` : line;
|
|
8035
8038
|
}
|
|
8036
|
-
function successfulDeploymentCopy(input2) {
|
|
8037
|
-
const connectedClients = input2.clients.length ? input2.clients.map((client) => ` \u2713 ${client}`).join("\n") : "
|
|
8039
|
+
function successfulDeploymentCopy(input2, theme = PLAIN) {
|
|
8040
|
+
const connectedClients = input2.clients.length ? input2.clients.map((client) => ` ${theme.tone("success", "\u2713")} ${client}`).join("\n") : ` ${theme.dim("No supported clients detected")}`;
|
|
8041
|
+
const field = (label, value) => ` ${theme.dim(`${label}:`)} ${value}`;
|
|
8038
8042
|
return [
|
|
8039
|
-
"Moodle MCP is ready.",
|
|
8043
|
+
theme.tone("success", "Moodle MCP is ready."),
|
|
8040
8044
|
"",
|
|
8041
|
-
"Endpoint",
|
|
8042
|
-
|
|
8045
|
+
theme.subject("Endpoint"),
|
|
8046
|
+
// The one line a person copies into a client, so it reads as an identifier, not a footnote.
|
|
8047
|
+
` ${theme.key(input2.endpoint)}`,
|
|
8043
8048
|
"",
|
|
8044
|
-
"Protocol",
|
|
8045
|
-
"
|
|
8046
|
-
|
|
8049
|
+
theme.subject("Protocol"),
|
|
8050
|
+
` ${theme.dim("MCP")} ${MODERN_PROTOCOL_VERSION}`,
|
|
8051
|
+
` ${theme.dim(`Stateless legacy compatibility: ${LEGACY_PROTOCOL_VERSION}`)}`,
|
|
8047
8052
|
"",
|
|
8048
|
-
"Moodle",
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
"
|
|
8053
|
+
theme.subject("Moodle"),
|
|
8054
|
+
field("Site", theme.target(input2.moodleSite)),
|
|
8055
|
+
field("User", input2.moodleUser),
|
|
8056
|
+
field("Session", theme.status("ready", { ready: "success" })),
|
|
8052
8057
|
"",
|
|
8053
|
-
"Renewal",
|
|
8054
|
-
"
|
|
8055
|
-
"
|
|
8058
|
+
theme.subject("Renewal"),
|
|
8059
|
+
` ${theme.dim("Installed on this computer")}`,
|
|
8060
|
+
` ${theme.dim("Next check: within 30 minutes")}`,
|
|
8056
8061
|
"",
|
|
8057
|
-
"Connected clients",
|
|
8062
|
+
theme.subject("Connected clients"),
|
|
8058
8063
|
connectedClients,
|
|
8059
8064
|
"",
|
|
8060
|
-
|
|
8065
|
+
`Run ${theme.key("moodle mcp status")} at any time.`
|
|
8061
8066
|
].join("\n");
|
|
8062
8067
|
}
|
|
8063
8068
|
|
|
@@ -8367,7 +8372,8 @@ var ManagedMcpDeployment = class {
|
|
|
8367
8372
|
clientsConnected: await this.dependencies.clients.inspect(profile),
|
|
8368
8373
|
readiness: "unknown",
|
|
8369
8374
|
readinessReasonCode: "NOT_DEPLOYED",
|
|
8370
|
-
sessionRevision: null
|
|
8375
|
+
sessionRevision: null,
|
|
8376
|
+
recoveryActive: false
|
|
8371
8377
|
};
|
|
8372
8378
|
}
|
|
8373
8379
|
const [worker, credentials, renewalInstalled, clientsConnected] = await Promise.all([
|
|
@@ -8396,7 +8402,8 @@ var ManagedMcpDeployment = class {
|
|
|
8396
8402
|
clientsConnected,
|
|
8397
8403
|
readiness,
|
|
8398
8404
|
readinessReasonCode,
|
|
8399
|
-
sessionRevision
|
|
8405
|
+
sessionRevision,
|
|
8406
|
+
recoveryActive: receipt.releaseDigest.endsWith("-recovery")
|
|
8400
8407
|
};
|
|
8401
8408
|
}
|
|
8402
8409
|
async recover(profile) {
|
|
@@ -9136,28 +9143,25 @@ var FetchManagedWorkerClient = class {
|
|
|
9136
9143
|
}
|
|
9137
9144
|
await this.mcpCall(input2.endpoint, input2.mcpAccessToken, "server/discover", {}, 1);
|
|
9138
9145
|
await this.mcpCall(input2.endpoint, input2.mcpAccessToken, "tools/list", {}, 2);
|
|
9139
|
-
const
|
|
9140
|
-
|
|
9141
|
-
input2.mcpAccessToken,
|
|
9142
|
-
"tools/call",
|
|
9143
|
-
{ name: "get_user", arguments: {} },
|
|
9144
|
-
3
|
|
9145
|
-
);
|
|
9146
|
-
const moodleUser = mcpUserFullname(userResult);
|
|
9146
|
+
const homeResult = await this.mcpCall(input2.endpoint, input2.mcpAccessToken, "tools/call", { name: "home", arguments: {} }, 3);
|
|
9147
|
+
const moodleUser = smokeMoodleUser(homeResult);
|
|
9147
9148
|
if (!moodleUser) {
|
|
9148
|
-
throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP
|
|
9149
|
+
throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP home check returned no Moodle user");
|
|
9149
9150
|
}
|
|
9150
|
-
const
|
|
9151
|
-
const
|
|
9152
|
-
if (!Array.isArray(
|
|
9153
|
-
if (
|
|
9154
|
-
const first2 =
|
|
9151
|
+
const unitsResult = await this.mcpCall(input2.endpoint, input2.mcpAccessToken, "tools/call", { name: "units", arguments: { limit: 1 } }, 4);
|
|
9152
|
+
const units = readableToolResult(unitsResult).units;
|
|
9153
|
+
if (!Array.isArray(units)) throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP units returned no unit list");
|
|
9154
|
+
if (units.length) {
|
|
9155
|
+
const first2 = units[0];
|
|
9155
9156
|
if (!isRecord9(first2) || !Number.isSafeInteger(first2.id) || Number(first2.id) <= 0) {
|
|
9156
|
-
throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP
|
|
9157
|
+
throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP units returned no usable unit ID");
|
|
9158
|
+
}
|
|
9159
|
+
const detail = readableToolResult(await this.mcpCall(input2.endpoint, input2.mcpAccessToken, "tools/call", { name: "unit", arguments: { unit: first2.id } }, 5));
|
|
9160
|
+
if (!isRecord9(detail.unit) || detail.unit.id !== first2.id) {
|
|
9161
|
+
throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP unit lookup did not match the listed unit");
|
|
9157
9162
|
}
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP course lookup did not match the listed course");
|
|
9163
|
+
if (detail.sections !== void 0 && !Array.isArray(detail.sections)) {
|
|
9164
|
+
throw new DeploymentApplyError("MCP_SMOKE_FAILED", "MCP unit lookup returned an unusable section list");
|
|
9161
9165
|
}
|
|
9162
9166
|
}
|
|
9163
9167
|
return { moodleUser };
|
|
@@ -9373,9 +9377,9 @@ function readableToolResult(result) {
|
|
|
9373
9377
|
}
|
|
9374
9378
|
throw new DeploymentApplyError("MCP_CONTENT_INCOMPLETE", "MCP text content is missing the complete structured result");
|
|
9375
9379
|
}
|
|
9376
|
-
function
|
|
9377
|
-
const
|
|
9378
|
-
return isRecord9(
|
|
9380
|
+
function smokeMoodleUser(result) {
|
|
9381
|
+
const home = readableToolResult(result).home;
|
|
9382
|
+
return isRecord9(home) && typeof home.name === "string" && home.name.trim() ? home.name.trim() : null;
|
|
9379
9383
|
}
|
|
9380
9384
|
function parseJsonOutput(output) {
|
|
9381
9385
|
const candidates = [output.indexOf("{"), output.indexOf("[")].filter((index) => index >= 0).sort((a, b) => a - b);
|
|
@@ -9862,6 +9866,7 @@ var DefaultMcpCommandService = class {
|
|
|
9862
9866
|
progressReporter;
|
|
9863
9867
|
async deploy(input2) {
|
|
9864
9868
|
await this.prepareToolchain(input2.yes);
|
|
9869
|
+
const theme = this.theme();
|
|
9865
9870
|
const progress = this.progress();
|
|
9866
9871
|
try {
|
|
9867
9872
|
progress.begin("Reading Cloudflare account and deployment state");
|
|
@@ -9904,8 +9909,8 @@ var DefaultMcpCommandService = class {
|
|
|
9904
9909
|
const events = [];
|
|
9905
9910
|
for await (const event2 of deployment.apply(plan)) {
|
|
9906
9911
|
events.push(event2);
|
|
9907
|
-
if (event2.status === "started") progress.begin(formatOnboardingStage(event2.stageId, "pending"));
|
|
9908
|
-
else if (event2.status === "completed") progress.end(formatOnboardingStage(event2.stageId, "completed"));
|
|
9912
|
+
if (event2.status === "started") progress.begin(formatOnboardingStage(event2.stageId, "pending", theme));
|
|
9913
|
+
else if (event2.status === "completed") progress.end(formatOnboardingStage(event2.stageId, "completed", theme));
|
|
9909
9914
|
else progress.clear();
|
|
9910
9915
|
}
|
|
9911
9916
|
progress.begin("Reading deployment status");
|
|
@@ -9928,7 +9933,7 @@ var DefaultMcpCommandService = class {
|
|
|
9928
9933
|
moodleSite: identity.moodleOrigin,
|
|
9929
9934
|
moodleUser: events.find((event2) => event2.moodleUser)?.moodleUser ?? "Unknown Moodle user",
|
|
9930
9935
|
clients: await this.connectedClientNames(identity.profile)
|
|
9931
|
-
})
|
|
9936
|
+
}, this.theme())
|
|
9932
9937
|
};
|
|
9933
9938
|
}
|
|
9934
9939
|
async planDeployment(deployment, initialIntent) {
|
|
@@ -10000,16 +10005,19 @@ var DefaultMcpCommandService = class {
|
|
|
10000
10005
|
...input2.verbose ? { serviceVersion: VERSION } : {},
|
|
10001
10006
|
...input2.logs ? { logs: { available: false, reason: "live_tail_required" } } : {}
|
|
10002
10007
|
};
|
|
10008
|
+
const theme = this.theme();
|
|
10009
|
+
const row = (label, value) => `${theme.dim(`${label}:`)} ${theme.status(value, { pass: "success", warn: "warning", fail: "danger", unknown: "muted", "not deployed": "warning", missing: "warning", "not connected": "warning", stored: "success", installed: "success", connected: "success" })}`;
|
|
10003
10010
|
return {
|
|
10004
10011
|
data,
|
|
10005
10012
|
text: [
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
...
|
|
10012
|
-
...
|
|
10013
|
+
row("Moodle MCP", managed.readiness),
|
|
10014
|
+
row("Worker", managed.worker?.workerName ?? "not deployed"),
|
|
10015
|
+
row("Credentials", managed.credentialsStored ? "stored" : "missing"),
|
|
10016
|
+
row("Renewal", managed.renewalInstalled ? "installed" : "missing"),
|
|
10017
|
+
row("Clients", managed.clientsConnected ? "connected" : "not connected"),
|
|
10018
|
+
...managed.recoveryActive ? [`${theme.dim("Release:")} ${theme.tone("warning", "the recovery Worker is live; OAuth sign-in is disabled until")} ${theme.key("moodle mcp deploy")} ${theme.tone("warning", "succeeds.")}`] : [],
|
|
10019
|
+
...updateAvailable ? [`${theme.dim("Update:")} remote Worker is behind this CLI. Run ${theme.key("moodle mcp deploy")} to update it.`] : [],
|
|
10020
|
+
...input2.logs ? [`${theme.dim("Logs:")} use a live sanitized tail from an interactive terminal`] : []
|
|
10013
10021
|
].join("\n")
|
|
10014
10022
|
};
|
|
10015
10023
|
}
|
|
@@ -10431,6 +10439,9 @@ Selection: `)).trim());
|
|
|
10431
10439
|
}
|
|
10432
10440
|
// Progress belongs on stderr so that --json and --yaml keep stdout to themselves.
|
|
10433
10441
|
// One shared reporter, so anything else that writes can stop the animation first.
|
|
10442
|
+
theme() {
|
|
10443
|
+
return createTheme4(this.options.color?.() ?? false);
|
|
10444
|
+
}
|
|
10434
10445
|
progress() {
|
|
10435
10446
|
this.progressReporter ??= createProgressReporter({ stream: this.options.stderr ?? process.stderr });
|
|
10436
10447
|
return this.progressReporter;
|
|
@@ -10674,7 +10685,9 @@ ${tryLines(["moodle due", "moodle units", "moodle --help"])}`}
|
|
|
10674
10685
|
stdin: io.stdin,
|
|
10675
10686
|
stdout,
|
|
10676
10687
|
stderr,
|
|
10677
|
-
fetchImpl: io.fetchImpl
|
|
10688
|
+
fetchImpl: io.fetchImpl,
|
|
10689
|
+
// Progress goes to stderr and the summary to stdout, so both streams must want colour.
|
|
10690
|
+
color: () => colorEnabled2(stdout, io.env) && colorEnabled2(stderr, io.env) && program.opts().color !== false
|
|
10678
10691
|
});
|
|
10679
10692
|
return mcpService;
|
|
10680
10693
|
};
|
|
@@ -10689,7 +10702,7 @@ ${tryLines(["moodle due", "moodle units", "moodle --help"])}`}
|
|
|
10689
10702
|
env: io.env ?? process.env,
|
|
10690
10703
|
format: outputFormat(program.opts(), stdout)
|
|
10691
10704
|
}) === "human";
|
|
10692
|
-
const theme = () =>
|
|
10705
|
+
const theme = () => createTheme5(colorEnabled2(stderr, io.env) && program.opts().color !== false);
|
|
10693
10706
|
const signIn = async (baseUrl) => {
|
|
10694
10707
|
const ui = createUi3({ input: io.stdin ?? process.stdin, output: stderr, interactive: true });
|
|
10695
10708
|
const auth2 = { env: io.env, fetch: io.fetchImpl, homeDir: io.homeDir, captureMobileToken: true };
|
package/dist/worker/recovery.js
CHANGED
|
@@ -26452,7 +26452,7 @@ function stringValue(value) {
|
|
|
26452
26452
|
}
|
|
26453
26453
|
|
|
26454
26454
|
// src/version.ts
|
|
26455
|
-
var VERSION = "0.9.
|
|
26455
|
+
var VERSION = "0.9.2";
|
|
26456
26456
|
|
|
26457
26457
|
// src/worker/http.ts
|
|
26458
26458
|
var HEALTH_PATH = "/healthz";
|
package/dist/worker/worker.js
CHANGED
|
@@ -26452,7 +26452,7 @@ function stringValue(value) {
|
|
|
26452
26452
|
}
|
|
26453
26453
|
|
|
26454
26454
|
// src/version.ts
|
|
26455
|
-
var VERSION = "0.9.
|
|
26455
|
+
var VERSION = "0.9.2";
|
|
26456
26456
|
|
|
26457
26457
|
// src/worker/http.ts
|
|
26458
26458
|
var HEALTH_PATH = "/healthz";
|