moodle-cli 0.7.0-alpha.0 → 0.7.0-alpha.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/moodle.js +143 -41
- package/dist/worker/worker.js +6 -6
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -174,7 +174,7 @@ moodle mcp bridge
|
|
|
174
174
|
|
|
175
175
|
The default client connection uses `moodle mcp bridge`, which keeps the Bearer token out of client configuration. Use `moodle mcp connect CLIENT --mode remote` for clients that support authenticated remote MCP headers.
|
|
176
176
|
|
|
177
|
-
Alpha version `0.7.0-alpha.
|
|
177
|
+
Alpha version `0.7.0-alpha.2` supports MCP `2026-07-28` and a stateless compatibility lane for `2025-11-25` clients.
|
|
178
178
|
|
|
179
179
|
### Configuration
|
|
180
180
|
|
package/dist/moodle.js
CHANGED
|
@@ -1861,7 +1861,7 @@ var MoodleClientCore = class {
|
|
|
1861
1861
|
constructor(baseUrl, options) {
|
|
1862
1862
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
1863
1863
|
const resolvedOptions = typeof options === "string" ? { cookie: { name: "MoodleSession", value: options } } : options;
|
|
1864
|
-
this.fetchImpl = resolvedOptions.fetchImpl ?? fetch;
|
|
1864
|
+
this.fetchImpl = resolvedOptions.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
1865
1865
|
this.cookie = resolvedOptions.cookie;
|
|
1866
1866
|
this.sesskey = resolvedOptions.pageContext?.sesskey ?? resolvedOptions.sesskey ?? null;
|
|
1867
1867
|
this.userid = resolvedOptions.pageContext?.user_info.userid ?? resolvedOptions.userid ?? null;
|
|
@@ -3700,7 +3700,7 @@ function escapeXml(value) {
|
|
|
3700
3700
|
}
|
|
3701
3701
|
|
|
3702
3702
|
// src/version.ts
|
|
3703
|
-
var VERSION = "0.7.0-alpha.
|
|
3703
|
+
var VERSION = "0.7.0-alpha.2";
|
|
3704
3704
|
|
|
3705
3705
|
// src/forum.ts
|
|
3706
3706
|
function parseDiscussionReference(value) {
|
|
@@ -5073,7 +5073,7 @@ var ManagedMcpDeployment = class {
|
|
|
5073
5073
|
`Worker ${intent.workerName} is not owned by Moodle MCP profile ${intent.profile}`
|
|
5074
5074
|
);
|
|
5075
5075
|
}
|
|
5076
|
-
const receipt = replacingExisting ? null : matchingReceipt;
|
|
5076
|
+
const receipt = replacingExisting || remote === null ? null : matchingReceipt;
|
|
5077
5077
|
const existing = remote && receipt ? { ...remote, productionEndpoint: receipt.productionEndpoint, releaseDigest: receipt.releaseDigest } : remote;
|
|
5078
5078
|
const rotate = intent.rotateToken === true && credentials !== null;
|
|
5079
5079
|
const releaseChanged = existing?.releaseDigest !== intent.releaseDigest;
|
|
@@ -5098,8 +5098,10 @@ var ManagedMcpDeployment = class {
|
|
|
5098
5098
|
let credentials = null;
|
|
5099
5099
|
let credentialsBefore = null;
|
|
5100
5100
|
let secretsUploaded = false;
|
|
5101
|
+
let initializedWorker = null;
|
|
5101
5102
|
let candidate = null;
|
|
5102
5103
|
let appliedReceipt = null;
|
|
5104
|
+
let productionRestored = false;
|
|
5103
5105
|
let moodleUser = null;
|
|
5104
5106
|
try {
|
|
5105
5107
|
yield started(activeStage);
|
|
@@ -5122,6 +5124,14 @@ var ManagedMcpDeployment = class {
|
|
|
5122
5124
|
activeStage = "upload_private_credentials";
|
|
5123
5125
|
yield started(activeStage);
|
|
5124
5126
|
if (plan.uploadCandidate) {
|
|
5127
|
+
if (plan.operation === "create") {
|
|
5128
|
+
initializedWorker = await this.dependencies.wrangler.initializeWorker({
|
|
5129
|
+
accountId: plan.intent.accountId,
|
|
5130
|
+
workerName: plan.intent.workerName,
|
|
5131
|
+
configPath: prepared.wranglerConfigPath,
|
|
5132
|
+
releaseDigest: plan.intent.releaseDigest
|
|
5133
|
+
});
|
|
5134
|
+
}
|
|
5125
5135
|
await this.dependencies.wrangler.uploadSecrets({
|
|
5126
5136
|
accountId: plan.intent.accountId,
|
|
5127
5137
|
workerName: plan.intent.workerName,
|
|
@@ -5134,17 +5144,30 @@ var ManagedMcpDeployment = class {
|
|
|
5134
5144
|
activeStage = "deploy_candidate_version";
|
|
5135
5145
|
yield started(activeStage);
|
|
5136
5146
|
if (plan.uploadCandidate) {
|
|
5147
|
+
const productionEndpoint2 = initializedWorker?.productionEndpoint ?? plan.existing?.productionEndpoint;
|
|
5148
|
+
if (!productionEndpoint2) {
|
|
5149
|
+
throw new DeploymentApplyError("MISSING_ENDPOINT", "The Worker production endpoint is unavailable");
|
|
5150
|
+
}
|
|
5137
5151
|
candidate = await this.dependencies.wrangler.uploadCandidate({
|
|
5138
5152
|
accountId: plan.intent.accountId,
|
|
5139
5153
|
workerName: plan.intent.workerName,
|
|
5140
5154
|
configPath: prepared.wranglerConfigPath,
|
|
5141
|
-
releaseDigest: plan.intent.releaseDigest
|
|
5155
|
+
releaseDigest: plan.intent.releaseDigest,
|
|
5156
|
+
productionEndpoint: productionEndpoint2
|
|
5142
5157
|
});
|
|
5158
|
+
if (!candidate.previewEndpoint) {
|
|
5159
|
+
await this.dependencies.wrangler.promote({
|
|
5160
|
+
accountId: plan.intent.accountId,
|
|
5161
|
+
workerName: plan.intent.workerName,
|
|
5162
|
+
versionId: candidate.versionId
|
|
5163
|
+
});
|
|
5164
|
+
promoted = true;
|
|
5165
|
+
}
|
|
5143
5166
|
}
|
|
5144
5167
|
yield completed(activeStage);
|
|
5145
5168
|
activeStage = "upload_moodle_session";
|
|
5146
5169
|
yield started(activeStage);
|
|
5147
|
-
const sessionEndpoint = candidate?.previewEndpoint ?? plan.existing?.productionEndpoint;
|
|
5170
|
+
const sessionEndpoint = candidate?.previewEndpoint ?? candidate?.productionEndpoint ?? plan.existing?.productionEndpoint;
|
|
5148
5171
|
if (!sessionEndpoint) {
|
|
5149
5172
|
throw new DeploymentApplyError("MISSING_ENDPOINT", "The Worker did not provide a session endpoint");
|
|
5150
5173
|
}
|
|
@@ -5162,7 +5185,7 @@ var ManagedMcpDeployment = class {
|
|
|
5162
5185
|
yield completed(activeStage);
|
|
5163
5186
|
activeStage = "run_release_checks";
|
|
5164
5187
|
yield started(activeStage);
|
|
5165
|
-
if (candidate) {
|
|
5188
|
+
if (candidate?.previewEndpoint) {
|
|
5166
5189
|
try {
|
|
5167
5190
|
await this.dependencies.worker.runSmoke({
|
|
5168
5191
|
endpoint: candidate.previewEndpoint,
|
|
@@ -5197,6 +5220,9 @@ var ManagedMcpDeployment = class {
|
|
|
5197
5220
|
if (!promoted) {
|
|
5198
5221
|
throw new DeploymentApplyError("PRODUCTION_VALIDATION_FAILED", "The existing Worker failed validation");
|
|
5199
5222
|
}
|
|
5223
|
+
if (plan.operation === "create") {
|
|
5224
|
+
throw new DeploymentApplyError("PRODUCTION_VALIDATION_FAILED", "The new Worker failed production validation");
|
|
5225
|
+
}
|
|
5200
5226
|
candidateRevision = await this.rollbackProduction(
|
|
5201
5227
|
plan,
|
|
5202
5228
|
credentials,
|
|
@@ -5204,6 +5230,7 @@ var ManagedMcpDeployment = class {
|
|
|
5204
5230
|
productionEndpoint,
|
|
5205
5231
|
candidateRevision
|
|
5206
5232
|
);
|
|
5233
|
+
productionRestored = true;
|
|
5207
5234
|
throw new DeploymentApplyError(
|
|
5208
5235
|
"PRODUCTION_VALIDATION_FAILED_RESTORED",
|
|
5209
5236
|
"The previous healthy release was restored with the current credentials and Moodle session"
|
|
@@ -5221,7 +5248,25 @@ var ManagedMcpDeployment = class {
|
|
|
5221
5248
|
if (plan.intent.rotateToken && credentialsBefore && !secretsUploaded) {
|
|
5222
5249
|
await this.dependencies.credentials.write(plan.intent.profile, credentialsBefore);
|
|
5223
5250
|
}
|
|
5224
|
-
if (
|
|
5251
|
+
if (promoted && plan.existing && !productionRestored && !appliedReceipt) {
|
|
5252
|
+
await this.dependencies.wrangler.restoreProduction({
|
|
5253
|
+
accountId: plan.intent.accountId,
|
|
5254
|
+
workerName: plan.intent.workerName,
|
|
5255
|
+
previousVersionId: plan.existing.productionVersionId
|
|
5256
|
+
});
|
|
5257
|
+
productionRestored = true;
|
|
5258
|
+
if (plan.intent.rotateToken && credentialsBefore) {
|
|
5259
|
+
await this.dependencies.credentials.write(plan.intent.profile, credentialsBefore);
|
|
5260
|
+
}
|
|
5261
|
+
}
|
|
5262
|
+
if (initializedWorker && !appliedReceipt) {
|
|
5263
|
+
await this.dependencies.wrangler.removeWorker({
|
|
5264
|
+
accountId: plan.intent.accountId,
|
|
5265
|
+
workerName: plan.intent.workerName,
|
|
5266
|
+
deploymentId: deploymentId(plan.intent.accountId, plan.intent.workerName)
|
|
5267
|
+
});
|
|
5268
|
+
await this.removeLocalState(plan.intent.profile);
|
|
5269
|
+
} else if (!appliedReceipt && plan.receipt && candidateRevision !== null) {
|
|
5225
5270
|
await this.dependencies.receipts.write({ ...plan.receipt, sessionRevision: candidateRevision });
|
|
5226
5271
|
}
|
|
5227
5272
|
const safe = asDeploymentError(error);
|
|
@@ -5458,6 +5503,9 @@ function validateIntent(intent) {
|
|
|
5458
5503
|
throw new DeploymentPlanError("INVALID_INTENT", "Cloudflare account and release digest are required");
|
|
5459
5504
|
}
|
|
5460
5505
|
}
|
|
5506
|
+
function deploymentId(accountId, workerName) {
|
|
5507
|
+
return `moodle-cli:${accountId}:${workerName}`;
|
|
5508
|
+
}
|
|
5461
5509
|
function isOwnedByProfile(worker, receipt, profile) {
|
|
5462
5510
|
return receipt ? receipt.accountId === worker.accountId && receipt.workerName === worker.workerName && receipt.deploymentId === worker.ownershipTag : worker.ownershipTag === `moodle-cli:${profile}`;
|
|
5463
5511
|
}
|
|
@@ -5930,6 +5978,8 @@ function isMissing3(error) {
|
|
|
5930
5978
|
|
|
5931
5979
|
// src/mcp/deployment/node-adapters.ts
|
|
5932
5980
|
var MODERN_MCP_VERSION = "2026-07-28";
|
|
5981
|
+
var WORKER_PROPAGATION_ATTEMPTS = 10;
|
|
5982
|
+
var WORKER_PROPAGATION_MAX_DELAY_MS = 4e3;
|
|
5933
5983
|
var NodeDeploymentCommandRunner = class {
|
|
5934
5984
|
async run(command, args, environment = {}) {
|
|
5935
5985
|
return new Promise((resolve, reject) => {
|
|
@@ -6016,12 +6066,12 @@ ${error.stderr}`)) {
|
|
|
6016
6066
|
return null;
|
|
6017
6067
|
}
|
|
6018
6068
|
const productionEndpoint = firstWorkersDevUrl(document) ?? `https://${workerName}.workers.dev`;
|
|
6019
|
-
const
|
|
6069
|
+
const deploymentId2 = ownershipId(accountId, workerName);
|
|
6020
6070
|
return {
|
|
6021
6071
|
accountId,
|
|
6022
6072
|
workerName,
|
|
6023
|
-
deploymentId,
|
|
6024
|
-
ownershipTag:
|
|
6073
|
+
deploymentId: deploymentId2,
|
|
6074
|
+
ownershipTag: deploymentId2,
|
|
6025
6075
|
productionEndpoint,
|
|
6026
6076
|
productionVersionId: versionIds[0],
|
|
6027
6077
|
previousHealthyVersionId: versionIds[1] ?? null,
|
|
@@ -6039,6 +6089,36 @@ ${error.stderr}`)) {
|
|
|
6039
6089
|
input.configPath
|
|
6040
6090
|
], input.accountId);
|
|
6041
6091
|
}
|
|
6092
|
+
async initializeWorker(input) {
|
|
6093
|
+
let result = null;
|
|
6094
|
+
try {
|
|
6095
|
+
result = await this.wrangler([
|
|
6096
|
+
"deploy",
|
|
6097
|
+
"--name",
|
|
6098
|
+
input.workerName,
|
|
6099
|
+
"--config",
|
|
6100
|
+
input.configPath,
|
|
6101
|
+
"--message",
|
|
6102
|
+
`moodle-cli-bootstrap:${input.releaseDigest}`
|
|
6103
|
+
], input.accountId);
|
|
6104
|
+
const worker = await this.inspect(input.accountId, input.workerName);
|
|
6105
|
+
if (!worker) {
|
|
6106
|
+
throw new DeploymentApplyError("INITIAL_WORKER_INVALID", "Wrangler did not return the initialized Worker");
|
|
6107
|
+
}
|
|
6108
|
+
const productionEndpoint = firstWorkersDevUrl([result.stdout, result.stderr]);
|
|
6109
|
+
return productionEndpoint ? { ...worker, productionEndpoint } : worker;
|
|
6110
|
+
} catch (error) {
|
|
6111
|
+
const worker = await this.inspect(input.accountId, input.workerName).catch(() => null);
|
|
6112
|
+
if (worker || result) {
|
|
6113
|
+
await this.removeWorker({
|
|
6114
|
+
accountId: input.accountId,
|
|
6115
|
+
workerName: input.workerName,
|
|
6116
|
+
deploymentId: ownershipId(input.accountId, input.workerName)
|
|
6117
|
+
}).catch(() => void 0);
|
|
6118
|
+
}
|
|
6119
|
+
throw error;
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6042
6122
|
async uploadCandidate(input) {
|
|
6043
6123
|
const outputFilePath = join7(dirname7(input.configPath), "wrangler-version-upload.jsonl");
|
|
6044
6124
|
await rm6(outputFilePath, { force: true });
|
|
@@ -6058,13 +6138,13 @@ ${error.stderr}`)) {
|
|
|
6058
6138
|
const document = await readWranglerVersionUpload(outputFilePath, input.workerName);
|
|
6059
6139
|
const versionId = typeof document.version_id === "string" ? document.version_id : null;
|
|
6060
6140
|
const previewEndpoint = firstWorkersDevUrl(document.preview_alias_url) ?? firstWorkersDevUrl(document.preview_url);
|
|
6061
|
-
if (!versionId
|
|
6062
|
-
throw new DeploymentApplyError("CANDIDATE_UPLOAD_INVALID", "Wrangler did not return a candidate version
|
|
6141
|
+
if (!versionId) {
|
|
6142
|
+
throw new DeploymentApplyError("CANDIDATE_UPLOAD_INVALID", "Wrangler did not return a candidate version");
|
|
6063
6143
|
}
|
|
6064
6144
|
return {
|
|
6065
6145
|
versionId,
|
|
6066
|
-
previewEndpoint,
|
|
6067
|
-
productionEndpoint:
|
|
6146
|
+
previewEndpoint: previewEndpoint ?? null,
|
|
6147
|
+
productionEndpoint: input.productionEndpoint,
|
|
6068
6148
|
deploymentId: ownershipId(input.accountId, input.workerName)
|
|
6069
6149
|
};
|
|
6070
6150
|
} finally {
|
|
@@ -6130,6 +6210,7 @@ var NodeReleaseMaterializer = class {
|
|
|
6130
6210
|
account_id: plan.intent.accountId,
|
|
6131
6211
|
main: `./${basename(workerFile)}`,
|
|
6132
6212
|
compatibility_date: this.options.compatibilityDate,
|
|
6213
|
+
preview_urls: true,
|
|
6133
6214
|
vars: { MOODLE_ORIGIN: plan.intent.moodleOrigin },
|
|
6134
6215
|
durable_objects: {
|
|
6135
6216
|
bindings: [{ name: "SESSION_BROKER", class_name: "SessionBroker" }]
|
|
@@ -6185,12 +6266,14 @@ function createBackgroundMoodleSessionSource(options = {}) {
|
|
|
6185
6266
|
return new DefaultMoodleSessionSource({ ...options, interactive: false });
|
|
6186
6267
|
}
|
|
6187
6268
|
var FetchManagedWorkerClient = class {
|
|
6188
|
-
constructor(fetchImpl =
|
|
6189
|
-
this.
|
|
6269
|
+
constructor(fetchImpl, sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))) {
|
|
6270
|
+
this.sleep = sleep;
|
|
6271
|
+
this.fetchImpl = fetchImpl ?? ((input, init) => fetch(input, init));
|
|
6190
6272
|
}
|
|
6273
|
+
sleep;
|
|
6191
6274
|
fetchImpl;
|
|
6192
6275
|
async putSession(input) {
|
|
6193
|
-
const response = await this.
|
|
6276
|
+
const response = await this.fetchWithRetry(endpointUrl(input.endpoint, "/session"), {
|
|
6194
6277
|
method: "PUT",
|
|
6195
6278
|
headers: {
|
|
6196
6279
|
authorization: `Bearer ${input.sessionSyncToken}`,
|
|
@@ -6202,18 +6285,18 @@ var FetchManagedWorkerClient = class {
|
|
|
6202
6285
|
cookieValue: input.session.cookieValue,
|
|
6203
6286
|
expectedRevision: input.expectedRevision
|
|
6204
6287
|
})
|
|
6205
|
-
});
|
|
6288
|
+
}, isRetryableSessionUpload);
|
|
6206
6289
|
const body = await safeJson(response);
|
|
6207
|
-
if (
|
|
6208
|
-
|
|
6209
|
-
throw new DeploymentApplyError(code, "The Worker rejected the Moodle session update");
|
|
6290
|
+
if (response.ok && isRecord8(body) && typeof body.revision === "number") {
|
|
6291
|
+
return { revision: body.revision };
|
|
6210
6292
|
}
|
|
6211
|
-
|
|
6293
|
+
const code = isRecord8(body) && typeof body.code === "string" ? body.code : "SESSION_UPLOAD_FAILED";
|
|
6294
|
+
throw new DeploymentApplyError(code, "The Worker rejected the Moodle session update");
|
|
6212
6295
|
}
|
|
6213
6296
|
async getReadiness(input) {
|
|
6214
|
-
const response = await this.
|
|
6297
|
+
const response = await this.fetchWithRetry(endpointUrl(input.endpoint, "/readyz"), {
|
|
6215
6298
|
headers: { authorization: `Bearer ${input.sessionSyncToken}` }
|
|
6216
|
-
});
|
|
6299
|
+
}, isRetryableWorkerPropagation);
|
|
6217
6300
|
const body = await safeJson(response);
|
|
6218
6301
|
if (isRecord8(body) && (body.status === "pass" || body.status === "warn" || body.status === "fail")) {
|
|
6219
6302
|
const session = firstHealthCheck(body, "moodle:session");
|
|
@@ -6227,7 +6310,11 @@ var FetchManagedWorkerClient = class {
|
|
|
6227
6310
|
return { status: "fail", reasonCode: null, revision: null };
|
|
6228
6311
|
}
|
|
6229
6312
|
async runSmoke(input) {
|
|
6230
|
-
const health = await this.
|
|
6313
|
+
const health = await this.fetchWithRetry(
|
|
6314
|
+
endpointUrl(input.endpoint, "/healthz"),
|
|
6315
|
+
void 0,
|
|
6316
|
+
isRetryableWorkerPropagation
|
|
6317
|
+
);
|
|
6231
6318
|
const healthBody = await safeJson(health);
|
|
6232
6319
|
if (!health.ok || !isRecord8(healthBody) || healthBody.status !== "pass") {
|
|
6233
6320
|
throw new DeploymentApplyError("HEALTH_CHECK_FAILED", "Worker liveness check failed");
|
|
@@ -6261,7 +6348,7 @@ var FetchManagedWorkerClient = class {
|
|
|
6261
6348
|
if (typeof params.name === "string") {
|
|
6262
6349
|
headers["mcp-name"] = params.name;
|
|
6263
6350
|
}
|
|
6264
|
-
const response = await this.
|
|
6351
|
+
const response = await this.fetchWithRetry(endpointUrl(endpoint, "/mcp"), {
|
|
6265
6352
|
method: "POST",
|
|
6266
6353
|
headers,
|
|
6267
6354
|
body: JSON.stringify({
|
|
@@ -6277,13 +6364,23 @@ var FetchManagedWorkerClient = class {
|
|
|
6277
6364
|
}
|
|
6278
6365
|
}
|
|
6279
6366
|
})
|
|
6280
|
-
});
|
|
6367
|
+
}, isRetryableWorkerPropagation);
|
|
6281
6368
|
const body = await safeJson(response);
|
|
6282
6369
|
if (!response.ok || !isRecord8(body) || body.jsonrpc !== "2.0" || body.id !== id || "error" in body || !("result" in body)) {
|
|
6283
6370
|
throw new DeploymentApplyError("MCP_SMOKE_FAILED", `MCP ${method} check failed`);
|
|
6284
6371
|
}
|
|
6285
6372
|
return body.result;
|
|
6286
6373
|
}
|
|
6374
|
+
async fetchWithRetry(input, init, retryable) {
|
|
6375
|
+
for (let attempt = 0; attempt < WORKER_PROPAGATION_ATTEMPTS; attempt += 1) {
|
|
6376
|
+
const response = await this.fetchImpl(input, init);
|
|
6377
|
+
if (!retryable(response.status) || attempt === WORKER_PROPAGATION_ATTEMPTS - 1) {
|
|
6378
|
+
return response;
|
|
6379
|
+
}
|
|
6380
|
+
await this.sleep(Math.min(500 * 2 ** attempt, WORKER_PROPAGATION_MAX_DELAY_MS));
|
|
6381
|
+
}
|
|
6382
|
+
throw new Error("Worker propagation retry loop exhausted unexpectedly");
|
|
6383
|
+
}
|
|
6287
6384
|
};
|
|
6288
6385
|
var PrivateDeploymentReceiptStore = class {
|
|
6289
6386
|
constructor(baseDirectory = join7(homedir8(), ".config", "moodle-cli", "mcp", "deployments")) {
|
|
@@ -6366,6 +6463,12 @@ function ownershipId(accountId, workerName) {
|
|
|
6366
6463
|
function endpointUrl(endpoint, path4) {
|
|
6367
6464
|
return `${endpoint.replace(/\/$/u, "")}${path4}`;
|
|
6368
6465
|
}
|
|
6466
|
+
function isRetryableSessionUpload(status) {
|
|
6467
|
+
return status === 401 || isRetryableWorkerPropagation(status);
|
|
6468
|
+
}
|
|
6469
|
+
function isRetryableWorkerPropagation(status) {
|
|
6470
|
+
return status === 404 || status === 429 || status >= 500;
|
|
6471
|
+
}
|
|
6369
6472
|
async function safeJson(response) {
|
|
6370
6473
|
try {
|
|
6371
6474
|
return await response.json();
|
|
@@ -6469,18 +6572,6 @@ function firstWorkersDevUrl(value) {
|
|
|
6469
6572
|
});
|
|
6470
6573
|
return found;
|
|
6471
6574
|
}
|
|
6472
|
-
function productionEndpointFromPreview(preview2, workerName) {
|
|
6473
|
-
const url = new URL(preview2);
|
|
6474
|
-
const labels = url.hostname.split(".");
|
|
6475
|
-
if (labels[0] !== workerName && labels[0]?.endsWith(`-${workerName}`)) {
|
|
6476
|
-
labels[0] = workerName;
|
|
6477
|
-
url.hostname = labels.join(".");
|
|
6478
|
-
}
|
|
6479
|
-
url.pathname = "";
|
|
6480
|
-
url.search = "";
|
|
6481
|
-
url.hash = "";
|
|
6482
|
-
return url.origin;
|
|
6483
|
-
}
|
|
6484
6575
|
function visit(value, visitor, key = "") {
|
|
6485
6576
|
visitor(key, value);
|
|
6486
6577
|
if (Array.isArray(value)) {
|
|
@@ -7860,7 +7951,10 @@ Log: ${result.log_path}`, options);
|
|
|
7860
7951
|
await outputMcpResult(runtime, result, options);
|
|
7861
7952
|
});
|
|
7862
7953
|
addOutputOptions(mcp.command("status").description("Show local and remote Moodle MCP readiness.")).option("--verbose", "Include sanitized deployment diagnostics.").option("--logs", "Include sanitized recent Worker logs.").action(async (options) => {
|
|
7863
|
-
const result = await getMcpService().status({
|
|
7954
|
+
const result = await getMcpService().status({
|
|
7955
|
+
verbose: Boolean(options.verbose || program.opts().verbose),
|
|
7956
|
+
logs: Boolean(options.logs)
|
|
7957
|
+
});
|
|
7864
7958
|
await outputMcpResult(runtime, result, options);
|
|
7865
7959
|
});
|
|
7866
7960
|
addOutputOptions(mutating(mcp.command("login").description("Acquire and upload a fresh Moodle session."))).action(
|
|
@@ -8095,7 +8189,15 @@ function queryMatches2(text, query) {
|
|
|
8095
8189
|
const needle = query.toLowerCase().split(/\s+/).join(" ");
|
|
8096
8190
|
return needle ? haystack.includes(needle) || needle.split(" ").every((token) => haystack.includes(token)) : true;
|
|
8097
8191
|
}
|
|
8098
|
-
|
|
8192
|
+
function pathsReferToSameFile(moduleUrl, executable) {
|
|
8193
|
+
if (!executable) return false;
|
|
8194
|
+
try {
|
|
8195
|
+
return realpathSync2(fileURLToPath2(moduleUrl)) === realpathSync2(executable);
|
|
8196
|
+
} catch {
|
|
8197
|
+
return false;
|
|
8198
|
+
}
|
|
8199
|
+
}
|
|
8200
|
+
var isMain = import.meta.main === true || pathsReferToSameFile(import.meta.url, process.argv[1]);
|
|
8099
8201
|
if (isMain) {
|
|
8100
8202
|
runCli().then((code) => {
|
|
8101
8203
|
process.exitCode = code;
|
package/dist/worker/worker.js
CHANGED
|
@@ -5996,7 +5996,7 @@ function problemResponse(status, code, title, detail, headers) {
|
|
|
5996
5996
|
}
|
|
5997
5997
|
|
|
5998
5998
|
// src/version.ts
|
|
5999
|
-
var VERSION = "0.7.0-alpha.
|
|
5999
|
+
var VERSION = "0.7.0-alpha.2";
|
|
6000
6000
|
|
|
6001
6001
|
// src/worker/http.ts
|
|
6002
6002
|
var HEALTH_PATH = "/healthz";
|
|
@@ -22117,7 +22117,7 @@ var MoodleClientCore = class {
|
|
|
22117
22117
|
constructor(baseUrl, options) {
|
|
22118
22118
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
22119
22119
|
const resolvedOptions = typeof options === "string" ? { cookie: { name: "MoodleSession", value: options } } : options;
|
|
22120
|
-
this.fetchImpl = resolvedOptions.fetchImpl ?? fetch;
|
|
22120
|
+
this.fetchImpl = resolvedOptions.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
22121
22121
|
this.cookie = resolvedOptions.cookie;
|
|
22122
22122
|
this.sesskey = resolvedOptions.pageContext?.sesskey ?? resolvedOptions.sesskey ?? null;
|
|
22123
22123
|
this.userid = resolvedOptions.pageContext?.user_info.userid ?? resolvedOptions.userid ?? null;
|
|
@@ -23214,12 +23214,12 @@ var AJAX_PATH = "/lib/ajax/service.php";
|
|
|
23214
23214
|
var SESSION_TOUCH = "core_session_touch";
|
|
23215
23215
|
var SESSION_TIME_REMAINING = "core_session_time_remaining";
|
|
23216
23216
|
var FetchMoodleSessionUpstream = class {
|
|
23217
|
-
|
|
23218
|
-
|
|
23217
|
+
origin;
|
|
23218
|
+
fetchImpl;
|
|
23219
|
+
constructor(origin, fetchImpl) {
|
|
23219
23220
|
this.origin = new URL(origin).origin;
|
|
23221
|
+
this.fetchImpl = fetchImpl ?? ((input, init) => fetch(input, init));
|
|
23220
23222
|
}
|
|
23221
|
-
fetchImpl;
|
|
23222
|
-
origin;
|
|
23223
23223
|
async validate(candidate) {
|
|
23224
23224
|
if (new URL(candidate.moodleOrigin).origin !== this.origin) return { valid: false, code: "SESSION_INVALID" };
|
|
23225
23225
|
const response = await this.fetchImpl(`${this.origin}${DASHBOARD_PATH2}`, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moodle-cli",
|
|
3
|
-
"version": "0.7.0-alpha.
|
|
3
|
+
"version": "0.7.0-alpha.2",
|
|
4
4
|
"description": "Terminal-first CLI for Moodle LMS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc --noEmit && tsup && npm run build:worker && rm -f dist/.gitignore",
|
|
21
21
|
"build:worker": "tsup --config tsup.worker.config.ts && node scripts/check-worker-bundle.mjs",
|
|
22
|
+
"bin:smoke": "bun scripts/smoke-standalone.mjs",
|
|
22
23
|
"check": "tsc --noEmit",
|
|
23
24
|
"pack:check": "npm pack --dry-run --json | node -e \"let data = ''; process.stdin.on('data', chunk => data += chunk); process.stdin.on('end', () => { const files = JSON.parse(data)[0]?.files?.map(file => file.path) ?? []; for (const required of ['dist/moodle.js', 'dist/worker/worker.js', 'README.md', 'ONBOARDING.md', 'SKILL.md', 'references/command-reference.md', 'references/downloads.md', 'agents/openai.yaml']) if (!files.includes(required)) throw new Error('npm package is missing ' + required); });\"",
|
|
24
25
|
"pack:smoke": "tmp=\"$(mktemp -d)\" && npm pack --pack-destination \"$tmp\" >/dev/null && npm install --prefix \"$tmp/install\" \"$tmp\"/moodle-cli-*.tgz >/dev/null && test \"$(\"$tmp/install/node_modules/.bin/moodle\" --version)\" = \"$(node -p \"JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).version\")\" && \"$tmp/install/node_modules/.bin/moodle\" download --help >/dev/null && \"$tmp/install/node_modules/.bin/moodle\" mcp --help >/dev/null",
|