moodle-cli 0.7.0-alpha.5 → 0.7.0-alpha.7
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/ONBOARDING.md +1 -1
- package/README.md +3 -3
- package/dist/moodle.js +206 -97
- package/dist/worker/worker.js +1 -1
- package/package.json +2 -2
package/ONBOARDING.md
CHANGED
|
@@ -19,7 +19,7 @@ Wait for the answer before doing setup work. Do not ask the student to find the
|
|
|
19
19
|
|
|
20
20
|
Once the student shares a URL, complete the setup on their computer:
|
|
21
21
|
|
|
22
|
-
- Check for Node.js 22+ and Bun. Install `moodle-cli` with the runtime available on the computer. Use `bunx --bun moodle-cli` as the command prefix when Bun is the only runtime.
|
|
22
|
+
- Check for Node.js 22.13+ and Bun (older Node cannot read browser cookies). Install `moodle-cli` with the runtime available on the computer. Use `bunx --bun moodle-cli` as the command prefix when Bun is the only runtime.
|
|
23
23
|
- Follow redirects and resolve the URL to the final Moodle origin in the form `https://host`. Remove the path, query, and fragment. Confirm that the origin serves Moodle before saving it.
|
|
24
24
|
- Read `~/.config/moodle-cli/config.yaml` if it exists. Set `base_url` to the verified origin and preserve the other settings.
|
|
25
25
|
- Tell the student that Moodle may open in their browser and that you will wait while they complete their university sign-in. Run `moodle auth login`. If a browser opens, let the student finish SSO there, then continue when the command returns.
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Let it keep up with deadlines and grades, fetch course files, and search forum d
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/moodle-cli)
|
|
8
8
|
[](https://github.com/bunizao/moodle-cli/actions/workflows/ci.yml)
|
|
9
|
-
[](https://nodejs.org/)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
10
|
[](https://bun.sh/)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
|
|
@@ -32,7 +32,7 @@ Your agent asks for your Moodle URL and opens your university's sign-in page whe
|
|
|
32
32
|
|
|
33
33
|
### Install and sign in manually
|
|
34
34
|
|
|
35
|
-
Use Node.js 22+ or Bun:
|
|
35
|
+
Use Node.js 22.13+ (needed for `node:sqlite`, which reads browser cookies) or Bun:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
# npm
|
|
@@ -176,7 +176,7 @@ moodle mcp bridge
|
|
|
176
176
|
|
|
177
177
|
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.
|
|
178
178
|
|
|
179
|
-
Alpha version `0.7.0-alpha.
|
|
179
|
+
Alpha version `0.7.0-alpha.7` supports MCP `2026-07-28` and a stateless compatibility lane for `2025-11-25` clients.
|
|
180
180
|
|
|
181
181
|
### Configuration
|
|
182
182
|
|
package/dist/moodle.js
CHANGED
|
@@ -222,8 +222,16 @@ async function getAuthenticatedSession(baseUrl, options = {}) {
|
|
|
222
222
|
if (cached) {
|
|
223
223
|
return cached;
|
|
224
224
|
}
|
|
225
|
+
const cookieWarnings = [];
|
|
226
|
+
const providerOptions = {
|
|
227
|
+
...options,
|
|
228
|
+
onCookieWarnings: (warnings) => {
|
|
229
|
+
cookieWarnings.push(...warnings);
|
|
230
|
+
options.onCookieWarnings?.(warnings);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
225
233
|
const browserProvider = options.browserCookieProvider ?? defaultBrowserCookieProvider;
|
|
226
|
-
const browserCookies = matchingMoodleSessionCookies(await browserProvider(baseUrl,
|
|
234
|
+
const browserCookies = matchingMoodleSessionCookies(await browserProvider(baseUrl, providerOptions), baseUrl);
|
|
227
235
|
const browserSession = await firstValidSession(baseUrl, browserCookies, validate);
|
|
228
236
|
if (browserSession) {
|
|
229
237
|
await refreshSessionCache(baseUrl, browserSession.cookie, browserSession.context, options);
|
|
@@ -247,10 +255,22 @@ async function getAuthenticatedSession(baseUrl, options = {}) {
|
|
|
247
255
|
return { baseUrl, cookie: refreshedSession.cookie, ...refreshedSession.context, fromCache: false };
|
|
248
256
|
}
|
|
249
257
|
}
|
|
250
|
-
throw new AuthError(
|
|
258
|
+
throw new AuthError(
|
|
259
|
+
`No usable MoodleSession found for ${baseUrl}.`,
|
|
260
|
+
authFailureHint(baseUrl, cookieWarnings, options.platform)
|
|
261
|
+
);
|
|
251
262
|
}
|
|
252
263
|
async function getAuthenticatedSessionWithBrowserFallback(baseUrl, options = {}) {
|
|
253
|
-
const
|
|
264
|
+
const cookieWarnings = [];
|
|
265
|
+
const authOptions = {
|
|
266
|
+
...options,
|
|
267
|
+
noCache: true,
|
|
268
|
+
nonInteractive: true,
|
|
269
|
+
onCookieWarnings: (warnings) => {
|
|
270
|
+
cookieWarnings.push(...warnings);
|
|
271
|
+
options.onCookieWarnings?.(warnings);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
254
274
|
const browserAuthOptions = {
|
|
255
275
|
...authOptions,
|
|
256
276
|
env: { ...options.env ?? process.env, [ENV_MOODLE_SESSION]: void 0 }
|
|
@@ -271,6 +291,12 @@ async function getAuthenticatedSessionWithBrowserFallback(baseUrl, options = {})
|
|
|
271
291
|
}
|
|
272
292
|
}
|
|
273
293
|
}
|
|
294
|
+
if (cookieAccessBlocked(cookieWarnings)) {
|
|
295
|
+
throw new AuthError(
|
|
296
|
+
`Cannot read browser cookies for ${baseUrl}.`,
|
|
297
|
+
cookieAccessHint(cookieWarnings, options.platform)
|
|
298
|
+
);
|
|
299
|
+
}
|
|
274
300
|
const url = loginUrl(baseUrl);
|
|
275
301
|
await (options.openBrowser ?? ((target) => openSystemBrowser(target, options)))(url);
|
|
276
302
|
options.onBrowserOpened?.(url);
|
|
@@ -330,7 +356,11 @@ async function defaultBrowserCookieProvider(baseUrl, options = {}) {
|
|
|
330
356
|
mode: "merge"
|
|
331
357
|
});
|
|
332
358
|
const braveProfiles = await braveProfilePaths(options);
|
|
333
|
-
const brave = braveProfiles.length ? await getCookies({ url: baseUrl, browsers: ["chrome"], chromeProfile: braveProfiles, mode: "merge" }) : { cookies: [] };
|
|
359
|
+
const brave = braveProfiles.length ? await getCookies({ url: baseUrl, browsers: ["chrome"], chromeProfile: braveProfiles, mode: "merge" }) : { cookies: [], warnings: [] };
|
|
360
|
+
const warnings = [...primary.warnings ?? [], ...brave.warnings ?? []];
|
|
361
|
+
if (warnings.length) {
|
|
362
|
+
options.onCookieWarnings?.(warnings);
|
|
363
|
+
}
|
|
334
364
|
return [...primary.cookies, ...brave.cookies].map((cookie) => ({
|
|
335
365
|
name: cookie.name,
|
|
336
366
|
value: cookie.value,
|
|
@@ -345,7 +375,7 @@ async function braveProfilePaths(options = {}) {
|
|
|
345
375
|
const roots = platform === "linux" ? [
|
|
346
376
|
join2(home, ".config/BraveSoftware/Brave-Browser"),
|
|
347
377
|
join2(home, ".var/app/com.brave.Browser/config/BraveSoftware/Brave-Browser")
|
|
348
|
-
] : platform === "win32" ? [join2(home, "AppData/Local/BraveSoftware/Brave-Browser/User Data")] : [];
|
|
378
|
+
] : platform === "win32" ? [join2(home, "AppData/Local/BraveSoftware/Brave-Browser/User Data")] : platform === "darwin" ? [join2(home, "Library/Application Support/BraveSoftware/Brave-Browser")] : [];
|
|
349
379
|
const profiles = [];
|
|
350
380
|
for (const root of roots) {
|
|
351
381
|
try {
|
|
@@ -375,13 +405,43 @@ async function loadSessionsFromOktaCli(baseUrl, options = {}, forceLogin = false
|
|
|
375
405
|
const refreshed = await readOktaCookies(executable, baseUrl, execFile2);
|
|
376
406
|
return refreshed.length ? refreshed : stored;
|
|
377
407
|
}
|
|
378
|
-
|
|
408
|
+
var COOKIE_ACCESS_DENIED = /EPERM|EACCES|operation not permitted|permission denied/i;
|
|
409
|
+
var COOKIE_SQLITE_UNAVAILABLE = /No such built-in module: node:sqlite/i;
|
|
410
|
+
var MINIMUM_NODE_FOR_BROWSER_COOKIES = "22.13.0";
|
|
411
|
+
function cookieAccessBlocked(warnings) {
|
|
412
|
+
return warnings.some((warning) => COOKIE_ACCESS_DENIED.test(warning) || COOKIE_SQLITE_UNAVAILABLE.test(warning));
|
|
413
|
+
}
|
|
414
|
+
function cookieAccessHint(warnings, platform = process.platform) {
|
|
415
|
+
const grant = platform === "darwin" ? "Grant Full Disk Access to the application running this command (System Settings > Privacy & Security > Full Disk Access), then restart it." : "Run this command as the user that owns the browser profile, or grant it read access to the browser cookie store.";
|
|
416
|
+
const remedy = warnings.some((warning) => COOKIE_SQLITE_UNAVAILABLE.test(warning)) ? [
|
|
417
|
+
`This Node.js runtime has no node:sqlite, which is needed to read browser cookies. Use Node.js ${MINIMUM_NODE_FOR_BROWSER_COOKIES} or newer, or run the CLI with Bun (bunx --bun moodle-cli).`
|
|
418
|
+
] : [
|
|
419
|
+
"If this runs inside a sandboxed app (an IDE or agent terminal), rerun it from a regular terminal first.",
|
|
420
|
+
grant
|
|
421
|
+
];
|
|
379
422
|
return [
|
|
423
|
+
"The browser cookie store could not be read, so the session could not be detected.",
|
|
424
|
+
...remedy,
|
|
425
|
+
`Alternatively set ${ENV_MOODLE_SESSION} to a valid MoodleSession cookie value.`,
|
|
426
|
+
"",
|
|
427
|
+
"Cookie store diagnostics:",
|
|
428
|
+
...warnings.map((warning) => ` - ${warning}`)
|
|
429
|
+
].join("\n");
|
|
430
|
+
}
|
|
431
|
+
function authFailureHint(baseUrl, cookieWarnings = [], platform = process.platform) {
|
|
432
|
+
if (cookieAccessBlocked(cookieWarnings)) {
|
|
433
|
+
return cookieAccessHint(cookieWarnings, platform);
|
|
434
|
+
}
|
|
435
|
+
const lines = [
|
|
380
436
|
`Log in to ${loginUrl(baseUrl)} in your browser, then rerun the command.`,
|
|
381
437
|
`Or set ${ENV_MOODLE_SESSION} to a valid MoodleSession cookie value.`,
|
|
382
438
|
`For automatic login, install okta-auth: ${OKTA_AUTH_INSTALL_COMMAND}, then run ${OKTA_AUTH_CONFIG_COMMAND}.`,
|
|
383
439
|
`okta-auth: ${OKTA_AUTH_URL}`
|
|
384
|
-
]
|
|
440
|
+
];
|
|
441
|
+
if (cookieWarnings.length) {
|
|
442
|
+
lines.push("", "Cookie store diagnostics:", ...cookieWarnings.map((warning) => ` - ${warning}`));
|
|
443
|
+
}
|
|
444
|
+
return lines.join("\n");
|
|
385
445
|
}
|
|
386
446
|
async function invalidateCachedSession(baseUrl, options = {}) {
|
|
387
447
|
await deleteCachedSession(baseUrl, cacheOptions(options));
|
|
@@ -3745,8 +3805,8 @@ function keepaliveProgramArguments(execPath = process.execPath, argv1 = process.
|
|
|
3745
3805
|
}
|
|
3746
3806
|
return [execPath, resolvedArgv1, ...tail];
|
|
3747
3807
|
}
|
|
3748
|
-
function buildKeepalivePlist(
|
|
3749
|
-
const args =
|
|
3808
|
+
function buildKeepalivePlist(programArguments2, intervalMinutes, logPath) {
|
|
3809
|
+
const args = programArguments2.map((arg) => ` <string>${escapeXml(arg)}</string>`).join("\n");
|
|
3750
3810
|
return [
|
|
3751
3811
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
3752
3812
|
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
@@ -3827,7 +3887,7 @@ function escapeXml(value) {
|
|
|
3827
3887
|
}
|
|
3828
3888
|
|
|
3829
3889
|
// src/version.ts
|
|
3830
|
-
var VERSION = "0.7.0-alpha.
|
|
3890
|
+
var VERSION = "0.7.0-alpha.7";
|
|
3831
3891
|
|
|
3832
3892
|
// src/forum.ts
|
|
3833
3893
|
function parseDiscussionReference(value) {
|
|
@@ -4412,7 +4472,7 @@ function jsonCodec(container) {
|
|
|
4412
4472
|
};
|
|
4413
4473
|
}
|
|
4414
4474
|
function jsonRegistration(connection) {
|
|
4415
|
-
return connection.mode === "bridge" ? { command: connection.command, args:
|
|
4475
|
+
return connection.mode === "bridge" ? { command: connection.command, args: connection.args } : {
|
|
4416
4476
|
type: "http",
|
|
4417
4477
|
url: connection.endpoint,
|
|
4418
4478
|
headers: { Authorization: `Bearer ${connection.accessToken}` }
|
|
@@ -4426,7 +4486,7 @@ function tomlBlock(registration, connection) {
|
|
|
4426
4486
|
if (connection.mode === "bridge") {
|
|
4427
4487
|
lines.push(
|
|
4428
4488
|
`command = ${JSON.stringify(connection.command)}`,
|
|
4429
|
-
`args = ${JSON.stringify(
|
|
4489
|
+
`args = ${JSON.stringify(connection.args)}`
|
|
4430
4490
|
);
|
|
4431
4491
|
} else {
|
|
4432
4492
|
lines.push(
|
|
@@ -4478,7 +4538,11 @@ function validateProfile(profile) {
|
|
|
4478
4538
|
}
|
|
4479
4539
|
function resolveConnection(options) {
|
|
4480
4540
|
if (options.mode !== "remote") {
|
|
4481
|
-
return {
|
|
4541
|
+
return {
|
|
4542
|
+
mode: "bridge",
|
|
4543
|
+
command: options.command ?? "moodle",
|
|
4544
|
+
args: [...options.commandArgs ?? [], "mcp", "bridge", "--profile", options.profile]
|
|
4545
|
+
};
|
|
4482
4546
|
}
|
|
4483
4547
|
if (!options.endpoint || !options.accessToken) {
|
|
4484
4548
|
throw new Error("Remote MCP connection requires an endpoint and access token");
|
|
@@ -4498,6 +4562,15 @@ function resolveConnection(options) {
|
|
|
4498
4562
|
return { mode: "remote", endpoint: endpoint.toString(), accessToken: options.accessToken };
|
|
4499
4563
|
}
|
|
4500
4564
|
|
|
4565
|
+
// src/mcp/self-command.ts
|
|
4566
|
+
function selfCommand(argv = process.argv, execPath = process.execPath) {
|
|
4567
|
+
const script = argv[1];
|
|
4568
|
+
return script && script !== execPath ? { command: execPath, args: [script] } : { command: execPath, args: [] };
|
|
4569
|
+
}
|
|
4570
|
+
function runtimeCommand(command, args) {
|
|
4571
|
+
return command ? { command, args: [...args ?? []] } : selfCommand();
|
|
4572
|
+
}
|
|
4573
|
+
|
|
4501
4574
|
// src/mcp/connectors/node-connectors.ts
|
|
4502
4575
|
import { chmod as chmod2, mkdir as mkdir4, readFile as readFile3, rm as rm3, stat as stat2, writeFile as writeFile4 } from "fs/promises";
|
|
4503
4576
|
import { homedir as homedir5 } from "os";
|
|
@@ -4530,9 +4603,11 @@ function createDefaultClientConnectors(profile, options = {}) {
|
|
|
4530
4603
|
const home = options.homeDirectory ?? homedir5();
|
|
4531
4604
|
const platform = options.platform ?? process.platform;
|
|
4532
4605
|
const fileSystem = options.fileSystem ?? new NodeConnectorFileSystem();
|
|
4606
|
+
const runtime = runtimeCommand(options.command, options.commandArgs);
|
|
4533
4607
|
const shared = {
|
|
4534
4608
|
profile,
|
|
4535
|
-
command:
|
|
4609
|
+
command: runtime.command,
|
|
4610
|
+
commandArgs: runtime.args,
|
|
4536
4611
|
mode: options.mode,
|
|
4537
4612
|
endpoint: options.endpoint,
|
|
4538
4613
|
accessToken: options.accessToken
|
|
@@ -5279,8 +5354,8 @@ var DeploymentPlanError = class extends Error {
|
|
|
5279
5354
|
code;
|
|
5280
5355
|
};
|
|
5281
5356
|
var DeploymentApplyError = class extends Error {
|
|
5282
|
-
constructor(code, message) {
|
|
5283
|
-
super(message);
|
|
5357
|
+
constructor(code, message, options) {
|
|
5358
|
+
super(message, options);
|
|
5284
5359
|
this.code = code;
|
|
5285
5360
|
this.name = "DeploymentApplyError";
|
|
5286
5361
|
}
|
|
@@ -5309,7 +5384,7 @@ var ManagedMcpDeployment = class {
|
|
|
5309
5384
|
const receipt = replacingExisting || remote === null ? null : matchingReceipt;
|
|
5310
5385
|
const existing = remote && receipt ? { ...remote, productionEndpoint: receipt.productionEndpoint, releaseDigest: receipt.releaseDigest } : remote;
|
|
5311
5386
|
const rotate = intent.rotateToken === true && credentials !== null;
|
|
5312
|
-
const releaseChanged = existing?.releaseDigest !== intent.releaseDigest;
|
|
5387
|
+
const releaseChanged = existing?.releaseDigest !== intent.releaseDigest || receipt?.restoredRelease === true;
|
|
5313
5388
|
const uploadCandidate = !existing || replacingExisting || releaseChanged || intent.repair === true || rotate;
|
|
5314
5389
|
return {
|
|
5315
5390
|
intent: { ...intent },
|
|
@@ -5356,20 +5431,13 @@ var ManagedMcpDeployment = class {
|
|
|
5356
5431
|
yield completed(activeStage);
|
|
5357
5432
|
activeStage = "upload_private_credentials";
|
|
5358
5433
|
yield started(activeStage);
|
|
5359
|
-
if (plan.uploadCandidate) {
|
|
5360
|
-
|
|
5361
|
-
initializedWorker = await this.dependencies.wrangler.initializeWorker({
|
|
5362
|
-
accountId: plan.intent.accountId,
|
|
5363
|
-
workerName: plan.intent.workerName,
|
|
5364
|
-
configPath: prepared.wranglerConfigPath,
|
|
5365
|
-
releaseDigest: plan.intent.releaseDigest
|
|
5366
|
-
});
|
|
5367
|
-
}
|
|
5368
|
-
await this.dependencies.wrangler.uploadSecrets({
|
|
5434
|
+
if (plan.uploadCandidate && plan.operation === "create") {
|
|
5435
|
+
initializedWorker = await this.dependencies.wrangler.initializeWorker({
|
|
5369
5436
|
accountId: plan.intent.accountId,
|
|
5370
5437
|
workerName: plan.intent.workerName,
|
|
5371
5438
|
configPath: prepared.wranglerConfigPath,
|
|
5372
|
-
secretsFilePath: prepared.secretsFilePath
|
|
5439
|
+
secretsFilePath: prepared.secretsFilePath,
|
|
5440
|
+
releaseDigest: plan.intent.releaseDigest
|
|
5373
5441
|
});
|
|
5374
5442
|
secretsUploaded = true;
|
|
5375
5443
|
}
|
|
@@ -5385,14 +5453,17 @@ var ManagedMcpDeployment = class {
|
|
|
5385
5453
|
accountId: plan.intent.accountId,
|
|
5386
5454
|
workerName: plan.intent.workerName,
|
|
5387
5455
|
configPath: prepared.wranglerConfigPath,
|
|
5456
|
+
secretsFilePath: prepared.secretsFilePath,
|
|
5388
5457
|
releaseDigest: plan.intent.releaseDigest,
|
|
5389
5458
|
productionEndpoint: productionEndpoint2
|
|
5390
5459
|
});
|
|
5460
|
+
secretsUploaded = true;
|
|
5391
5461
|
if (!candidate.previewEndpoint) {
|
|
5392
5462
|
await this.dependencies.wrangler.promote({
|
|
5393
5463
|
accountId: plan.intent.accountId,
|
|
5394
5464
|
workerName: plan.intent.workerName,
|
|
5395
|
-
versionId: candidate.versionId
|
|
5465
|
+
versionId: candidate.versionId,
|
|
5466
|
+
releaseDigest: plan.intent.releaseDigest
|
|
5396
5467
|
});
|
|
5397
5468
|
promoted = true;
|
|
5398
5469
|
}
|
|
@@ -5434,7 +5505,8 @@ var ManagedMcpDeployment = class {
|
|
|
5434
5505
|
await this.dependencies.wrangler.promote({
|
|
5435
5506
|
accountId: plan.intent.accountId,
|
|
5436
5507
|
workerName: plan.intent.workerName,
|
|
5437
|
-
versionId: candidate.versionId
|
|
5508
|
+
versionId: candidate.versionId,
|
|
5509
|
+
releaseDigest: plan.intent.releaseDigest
|
|
5438
5510
|
});
|
|
5439
5511
|
promoted = true;
|
|
5440
5512
|
}
|
|
@@ -5535,15 +5607,14 @@ var ManagedMcpDeployment = class {
|
|
|
5535
5607
|
let readinessReasonCode = null;
|
|
5536
5608
|
let sessionRevision = null;
|
|
5537
5609
|
if (worker && credentials) {
|
|
5538
|
-
const
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
});
|
|
5610
|
+
const target = { endpoint: receipt.productionEndpoint, sessionSyncToken: credentials.sessionSyncToken };
|
|
5611
|
+
await this.dependencies.worker.touchSession(target);
|
|
5612
|
+
const remoteReadiness = await this.dependencies.worker.getReadiness(target);
|
|
5542
5613
|
readiness = remoteReadiness.status;
|
|
5543
5614
|
readinessReasonCode = remoteReadiness.reasonCode;
|
|
5544
5615
|
sessionRevision = remoteReadiness.revision;
|
|
5545
5616
|
}
|
|
5546
|
-
const resolvedWorker = worker ? { ...worker, productionEndpoint: receipt.productionEndpoint } : null;
|
|
5617
|
+
const resolvedWorker = worker ? { ...worker, productionEndpoint: receipt.productionEndpoint, releaseDigest: worker.releaseDigest || receipt.releaseDigest } : null;
|
|
5547
5618
|
return {
|
|
5548
5619
|
profile,
|
|
5549
5620
|
worker: resolvedWorker,
|
|
@@ -5606,6 +5677,7 @@ var ManagedMcpDeployment = class {
|
|
|
5606
5677
|
await this.dependencies.receipts.write({
|
|
5607
5678
|
...receipt,
|
|
5608
5679
|
productionVersionId: worker.previousHealthyVersionId,
|
|
5680
|
+
...swappedDigests(receipt),
|
|
5609
5681
|
sessionRevision: upload.revision
|
|
5610
5682
|
});
|
|
5611
5683
|
await this.reconcileLocalIntegrations(profile);
|
|
@@ -5652,7 +5724,8 @@ var ManagedMcpDeployment = class {
|
|
|
5652
5724
|
}
|
|
5653
5725
|
await this.dependencies.receipts.write({
|
|
5654
5726
|
...receipt,
|
|
5655
|
-
productionVersionId: previousVersionId
|
|
5727
|
+
productionVersionId: previousVersionId,
|
|
5728
|
+
...swappedDigests(receipt)
|
|
5656
5729
|
});
|
|
5657
5730
|
return { status: "restored", versionId: previousVersionId };
|
|
5658
5731
|
}
|
|
@@ -5759,6 +5832,7 @@ function makeReceipt(plan, candidate, sessionRevision) {
|
|
|
5759
5832
|
productionEndpoint: candidate.productionEndpoint,
|
|
5760
5833
|
productionVersionId: candidate.versionId,
|
|
5761
5834
|
releaseDigest: plan.intent.releaseDigest,
|
|
5835
|
+
...previousDigest(plan.existing?.releaseDigest),
|
|
5762
5836
|
sessionRevision
|
|
5763
5837
|
};
|
|
5764
5838
|
}
|
|
@@ -5774,9 +5848,16 @@ function makeReceipt(plan, candidate, sessionRevision) {
|
|
|
5774
5848
|
productionEndpoint: plan.existing.productionEndpoint,
|
|
5775
5849
|
productionVersionId: plan.existing.productionVersionId,
|
|
5776
5850
|
releaseDigest: plan.existing.releaseDigest,
|
|
5851
|
+
...previousDigest(plan.receipt?.previousReleaseDigest),
|
|
5777
5852
|
sessionRevision
|
|
5778
5853
|
};
|
|
5779
5854
|
}
|
|
5855
|
+
function previousDigest(digest2) {
|
|
5856
|
+
return digest2 === void 0 ? {} : { previousReleaseDigest: digest2 };
|
|
5857
|
+
}
|
|
5858
|
+
function swappedDigests(receipt) {
|
|
5859
|
+
return { releaseDigest: receipt.previousReleaseDigest ?? "", previousReleaseDigest: receipt.releaseDigest, restoredRelease: true };
|
|
5860
|
+
}
|
|
5780
5861
|
function started(stageId) {
|
|
5781
5862
|
return event(stageId, "started");
|
|
5782
5863
|
}
|
|
@@ -5797,7 +5878,8 @@ function asDeploymentError(error) {
|
|
|
5797
5878
|
if (error instanceof DeploymentApplyError) {
|
|
5798
5879
|
return error;
|
|
5799
5880
|
}
|
|
5800
|
-
|
|
5881
|
+
const detail = error instanceof Error && error.message ? `: ${error.message}` : "";
|
|
5882
|
+
return new DeploymentApplyError("DEPLOYMENT_FAILED", `The managed Moodle MCP deployment failed${detail}`, { cause: error });
|
|
5801
5883
|
}
|
|
5802
5884
|
|
|
5803
5885
|
// src/mcp/deployment/node-adapters.ts
|
|
@@ -5945,17 +6027,19 @@ function macOSPlan(options, intervalMinutes) {
|
|
|
5945
6027
|
const label = `com.moodle-cli.mcp-renewal.${options.profile}`;
|
|
5946
6028
|
const path4 = `${trimEnd(options.homeDirectory, "/")}/Library/LaunchAgents/${label}.plist`;
|
|
5947
6029
|
const target = `gui/${options.uid}`;
|
|
5948
|
-
const
|
|
6030
|
+
const logPath = `${trimEnd(options.homeDirectory, "/")}/Library/Logs/${label}.log`;
|
|
5949
6031
|
const plist = [
|
|
5950
6032
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
5951
6033
|
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
5952
6034
|
'<plist version="1.0"><dict>',
|
|
5953
6035
|
`<key>Label</key><string>${xml(label)}</string>`,
|
|
5954
6036
|
"<key>ProgramArguments</key><array>",
|
|
5955
|
-
...
|
|
6037
|
+
...programArguments(options).map((arg) => `<string>${xml(arg)}</string>`),
|
|
5956
6038
|
"</array>",
|
|
5957
6039
|
`<key>StartInterval</key><integer>${intervalMinutes * 60}</integer>`,
|
|
5958
6040
|
"<key>RunAtLoad</key><true/>",
|
|
6041
|
+
`<key>StandardOutPath</key><string>${xml(logPath)}</string>`,
|
|
6042
|
+
`<key>StandardErrorPath</key><string>${xml(logPath)}</string>`,
|
|
5959
6043
|
"</dict></plist>",
|
|
5960
6044
|
""
|
|
5961
6045
|
].join("\n");
|
|
@@ -5976,7 +6060,7 @@ function linuxPlan(options, intervalMinutes) {
|
|
|
5976
6060
|
const directory = `${trimEnd(options.homeDirectory, "/")}/.config/systemd/user`;
|
|
5977
6061
|
const servicePath = `${directory}/${label}.service`;
|
|
5978
6062
|
const timerPath = `${directory}/${label}.timer`;
|
|
5979
|
-
const command =
|
|
6063
|
+
const command = programArguments(options).map(systemdQuote).join(" ");
|
|
5980
6064
|
const service = [
|
|
5981
6065
|
"[Unit]",
|
|
5982
6066
|
`Description=Moodle MCP session renewal (${options.profile})`,
|
|
@@ -6020,7 +6104,7 @@ function linuxPlan(options, intervalMinutes) {
|
|
|
6020
6104
|
function windowsPlan(options, intervalMinutes) {
|
|
6021
6105
|
const label = `Moodle CLI MCP Renewal (${options.profile})`;
|
|
6022
6106
|
const path4 = `${trimEnd(options.homeDirectory, "\\/")}\\AppData\\Local\\moodle-cli\\renewal\\${options.profile}.xml`;
|
|
6023
|
-
const argumentsText = renewalArgs(options.profile).map(windowsArgument).join(" ");
|
|
6107
|
+
const argumentsText = [...options.executableArgs ?? [], ...renewalArgs(options.profile)].map(windowsArgument).join(" ");
|
|
6024
6108
|
const task = [
|
|
6025
6109
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
6026
6110
|
'<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">',
|
|
@@ -6047,6 +6131,9 @@ function windowsPlan(options, intervalMinutes) {
|
|
|
6047
6131
|
function renewalArgs(profile) {
|
|
6048
6132
|
return ["mcp", "renewal", "run", "--profile", profile, "--json"];
|
|
6049
6133
|
}
|
|
6134
|
+
function programArguments(options) {
|
|
6135
|
+
return [options.executable, ...options.executableArgs ?? [], ...renewalArgs(options.profile)];
|
|
6136
|
+
}
|
|
6050
6137
|
function validateOptions(options) {
|
|
6051
6138
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(options.profile)) {
|
|
6052
6139
|
throw new Error("Invalid renewal profile name");
|
|
@@ -6054,6 +6141,9 @@ function validateOptions(options) {
|
|
|
6054
6141
|
if (!options.executable || /[\r\n]/.test(options.executable)) {
|
|
6055
6142
|
throw new Error("Invalid renewal executable path");
|
|
6056
6143
|
}
|
|
6144
|
+
if ((options.executableArgs ?? []).some((arg) => /[\r\n]/.test(arg))) {
|
|
6145
|
+
throw new Error("Invalid renewal executable arguments");
|
|
6146
|
+
}
|
|
6057
6147
|
if (!options.homeDirectory || /[\r\n]/.test(options.homeDirectory)) {
|
|
6058
6148
|
throw new Error("Invalid renewal home directory");
|
|
6059
6149
|
}
|
|
@@ -6128,10 +6218,12 @@ function createDefaultRenewalInstaller(profile, options = {}) {
|
|
|
6128
6218
|
if (!isSupportedPlatform(platform)) {
|
|
6129
6219
|
throw new Error(`Moodle MCP renewal is not supported on ${platform}`);
|
|
6130
6220
|
}
|
|
6221
|
+
const runtime = runtimeCommand(options.executable, options.executableArgs);
|
|
6131
6222
|
const plan = buildRenewalInstallPlan({
|
|
6132
6223
|
platform,
|
|
6133
6224
|
profile,
|
|
6134
|
-
executable:
|
|
6225
|
+
executable: runtime.command,
|
|
6226
|
+
executableArgs: runtime.args,
|
|
6135
6227
|
homeDirectory: options.homeDirectory ?? homedir7(),
|
|
6136
6228
|
uid: options.uid ?? (typeof process.getuid === "function" ? process.getuid() : void 0),
|
|
6137
6229
|
intervalMinutes: options.intervalMinutes
|
|
@@ -6242,7 +6334,7 @@ var NodeDeploymentCommandRunner = class {
|
|
|
6242
6334
|
};
|
|
6243
6335
|
var WranglerCommandError = class extends Error {
|
|
6244
6336
|
constructor(exitCode, stdout, stderr) {
|
|
6245
|
-
super(
|
|
6337
|
+
super(wranglerFailureMessage(stderr, stdout));
|
|
6246
6338
|
this.exitCode = exitCode;
|
|
6247
6339
|
this.stdout = stdout;
|
|
6248
6340
|
this.stderr = stderr;
|
|
@@ -6252,6 +6344,13 @@ var WranglerCommandError = class extends Error {
|
|
|
6252
6344
|
stdout;
|
|
6253
6345
|
stderr;
|
|
6254
6346
|
};
|
|
6347
|
+
function wranglerFailureMessage(stderr, stdout) {
|
|
6348
|
+
const lines = `${stderr}
|
|
6349
|
+
${stdout}`.replace(/\u001B\[[0-9;]*m/gu, "").split(/\r?\n/u).map((line) => line.trim());
|
|
6350
|
+
const errorIndex = lines.findIndex((line) => line.includes("[ERROR]"));
|
|
6351
|
+
const detail = errorIndex >= 0 ? lines.slice(errorIndex).filter((line) => line && !/^(To learn more|If you think this is a bug|Logs were written)/u.test(line)).slice(0, 3).join(" ").replace(/^.*\[ERROR\]\s*/u, "") : "";
|
|
6352
|
+
return detail ? `Packaged Wrangler command failed: ${detail.slice(0, 600)}` : "Packaged Wrangler command failed";
|
|
6353
|
+
}
|
|
6255
6354
|
var NodeWranglerDeploymentAdapter = class {
|
|
6256
6355
|
wranglerBinPath;
|
|
6257
6356
|
runner;
|
|
@@ -6294,8 +6393,8 @@ ${error.stderr}`)) {
|
|
|
6294
6393
|
throw error;
|
|
6295
6394
|
}
|
|
6296
6395
|
const document = parseJsonOutput(result.stdout);
|
|
6297
|
-
const
|
|
6298
|
-
if (!
|
|
6396
|
+
const [current, previous] = deploymentHistory(document);
|
|
6397
|
+
if (!current) {
|
|
6299
6398
|
return null;
|
|
6300
6399
|
}
|
|
6301
6400
|
const productionEndpoint = firstWorkersDevUrl(document) ?? `https://${workerName}.workers.dev`;
|
|
@@ -6306,22 +6405,11 @@ ${error.stderr}`)) {
|
|
|
6306
6405
|
deploymentId: deploymentId2,
|
|
6307
6406
|
ownershipTag: deploymentId2,
|
|
6308
6407
|
productionEndpoint,
|
|
6309
|
-
productionVersionId:
|
|
6310
|
-
previousHealthyVersionId:
|
|
6311
|
-
releaseDigest:
|
|
6408
|
+
productionVersionId: current.versionId,
|
|
6409
|
+
previousHealthyVersionId: previous?.versionId ?? null,
|
|
6410
|
+
releaseDigest: releaseDigestFromMessage(current.message) ?? ""
|
|
6312
6411
|
};
|
|
6313
6412
|
}
|
|
6314
|
-
async uploadSecrets(input) {
|
|
6315
|
-
await this.wrangler([
|
|
6316
|
-
"secret",
|
|
6317
|
-
"bulk",
|
|
6318
|
-
input.secretsFilePath,
|
|
6319
|
-
"--name",
|
|
6320
|
-
input.workerName,
|
|
6321
|
-
"--config",
|
|
6322
|
-
input.configPath
|
|
6323
|
-
], input.accountId);
|
|
6324
|
-
}
|
|
6325
6413
|
async initializeWorker(input) {
|
|
6326
6414
|
let result = null;
|
|
6327
6415
|
try {
|
|
@@ -6331,6 +6419,8 @@ ${error.stderr}`)) {
|
|
|
6331
6419
|
input.workerName,
|
|
6332
6420
|
"--config",
|
|
6333
6421
|
input.configPath,
|
|
6422
|
+
"--secrets-file",
|
|
6423
|
+
input.secretsFilePath,
|
|
6334
6424
|
"--message",
|
|
6335
6425
|
`moodle-cli-bootstrap:${input.releaseDigest}`
|
|
6336
6426
|
], input.accountId);
|
|
@@ -6365,6 +6455,8 @@ ${error.stderr}`)) {
|
|
|
6365
6455
|
input.workerName,
|
|
6366
6456
|
"--config",
|
|
6367
6457
|
input.configPath,
|
|
6458
|
+
"--secrets-file",
|
|
6459
|
+
input.secretsFilePath,
|
|
6368
6460
|
"--preview-alias",
|
|
6369
6461
|
"moodle-cli-candidate",
|
|
6370
6462
|
"--message",
|
|
@@ -6386,6 +6478,8 @@ ${error.stderr}`)) {
|
|
|
6386
6478
|
await rm6(outputFilePath, { force: true });
|
|
6387
6479
|
}
|
|
6388
6480
|
}
|
|
6481
|
+
// restoreProduction re-deploys an older version whose digest is unknown, so the
|
|
6482
|
+
// release annotation is only written when the caller knows it.
|
|
6389
6483
|
async promote(input) {
|
|
6390
6484
|
await this.wrangler([
|
|
6391
6485
|
"versions",
|
|
@@ -6393,7 +6487,8 @@ ${error.stderr}`)) {
|
|
|
6393
6487
|
`${input.versionId}@100`,
|
|
6394
6488
|
"--name",
|
|
6395
6489
|
input.workerName,
|
|
6396
|
-
"--yes"
|
|
6490
|
+
"--yes",
|
|
6491
|
+
...input.releaseDigest ? ["--message", `moodle-cli-release:${input.releaseDigest}`] : []
|
|
6397
6492
|
], input.accountId);
|
|
6398
6493
|
}
|
|
6399
6494
|
async restoreProduction(input) {
|
|
@@ -6488,7 +6583,7 @@ var DefaultMoodleSessionSource = class {
|
|
|
6488
6583
|
}
|
|
6489
6584
|
options;
|
|
6490
6585
|
async loadValidated(_profile, moodleOrigin) {
|
|
6491
|
-
const session = this.options.interactive === false ? await getAuthenticatedSession(moodleOrigin, { ...this.options, nonInteractive: true }) : await getAuthenticatedSessionWithBrowserFallback(moodleOrigin, this.options);
|
|
6586
|
+
const session = this.options.interactive === false ? await getAuthenticatedSession(moodleOrigin, { ...this.options, nonInteractive: true, noCache: true }) : await getAuthenticatedSessionWithBrowserFallback(moodleOrigin, this.options);
|
|
6492
6587
|
return {
|
|
6493
6588
|
moodleOrigin,
|
|
6494
6589
|
cookieName: session.cookie.name,
|
|
@@ -6530,7 +6625,7 @@ var FetchManagedWorkerClient = class {
|
|
|
6530
6625
|
return { revision: body.revision };
|
|
6531
6626
|
}
|
|
6532
6627
|
const code = isRecord8(body) && typeof body.code === "string" ? body.code : "SESSION_UPLOAD_FAILED";
|
|
6533
|
-
throw new DeploymentApplyError(code,
|
|
6628
|
+
throw new DeploymentApplyError(code, `The Worker rejected the Moodle session update (${code})`);
|
|
6534
6629
|
}
|
|
6535
6630
|
async getReadiness(input) {
|
|
6536
6631
|
const response = await this.fetchWithRetry(endpointUrl(input.endpoint, "/readyz"), {
|
|
@@ -6548,6 +6643,12 @@ var FetchManagedWorkerClient = class {
|
|
|
6548
6643
|
}
|
|
6549
6644
|
return { status: "fail", reasonCode: null, revision: null };
|
|
6550
6645
|
}
|
|
6646
|
+
async touchSession(input) {
|
|
6647
|
+
await this.fetchWithRetry(endpointUrl(input.endpoint, "/session/touch"), {
|
|
6648
|
+
method: "POST",
|
|
6649
|
+
headers: { authorization: `Bearer ${input.sessionSyncToken}` }
|
|
6650
|
+
}, isRetryableWorkerRouting);
|
|
6651
|
+
}
|
|
6551
6652
|
async runSmoke(input) {
|
|
6552
6653
|
const health = await this.fetchWithRetry(
|
|
6553
6654
|
endpointUrl(input.endpoint, "/healthz"),
|
|
@@ -6657,7 +6758,7 @@ var PrivateDeploymentReceiptStore = class {
|
|
|
6657
6758
|
function createDefaultManagedDeployment(options) {
|
|
6658
6759
|
const homeDirectory = options.homeDirectory ?? homedir8();
|
|
6659
6760
|
const platform = options.platform ?? process.platform;
|
|
6660
|
-
const
|
|
6761
|
+
const runtime = runtimeCommand(options.executable, options.executableArgs);
|
|
6661
6762
|
const defaults = {
|
|
6662
6763
|
wrangler: new NodeWranglerDeploymentAdapter({ wranglerBinPath: options.wranglerBinPath }),
|
|
6663
6764
|
materializer: new NodeReleaseMaterializer({
|
|
@@ -6671,14 +6772,16 @@ function createDefaultManagedDeployment(options) {
|
|
|
6671
6772
|
...options.renewal,
|
|
6672
6773
|
platform,
|
|
6673
6774
|
homeDirectory,
|
|
6674
|
-
executable,
|
|
6775
|
+
executable: runtime.command,
|
|
6776
|
+
executableArgs: runtime.args,
|
|
6675
6777
|
uid: options.uid
|
|
6676
6778
|
}),
|
|
6677
6779
|
clients: new DefaultClientIntegration({
|
|
6678
6780
|
...options.connector,
|
|
6679
6781
|
platform,
|
|
6680
6782
|
homeDirectory,
|
|
6681
|
-
command:
|
|
6783
|
+
command: runtime.command,
|
|
6784
|
+
commandArgs: runtime.args
|
|
6682
6785
|
}),
|
|
6683
6786
|
receipts: new PrivateDeploymentReceiptStore(join7(homeDirectory, ".config", "moodle-cli", "mcp", "deployments")),
|
|
6684
6787
|
createToken: () => randomBytes(32).toString("base64url")
|
|
@@ -6737,6 +6840,9 @@ function isRetryableSessionUpload(status) {
|
|
|
6737
6840
|
function isRetryableWorkerPropagation(status) {
|
|
6738
6841
|
return status === 404 || status === 429 || status >= 500;
|
|
6739
6842
|
}
|
|
6843
|
+
function isRetryableWorkerRouting(status) {
|
|
6844
|
+
return status === 404 || status === 429;
|
|
6845
|
+
}
|
|
6740
6846
|
async function safeJson(response) {
|
|
6741
6847
|
try {
|
|
6742
6848
|
return await response.json();
|
|
@@ -6789,30 +6895,35 @@ async function readWranglerVersionUpload(outputFilePath, workerName) {
|
|
|
6789
6895
|
}
|
|
6790
6896
|
throw new DeploymentApplyError("CANDIDATE_UPLOAD_INVALID", "Wrangler wrote unsupported candidate metadata");
|
|
6791
6897
|
}
|
|
6792
|
-
function
|
|
6793
|
-
const
|
|
6794
|
-
visit(value, (
|
|
6795
|
-
if (
|
|
6796
|
-
|
|
6898
|
+
function deploymentHistory(value) {
|
|
6899
|
+
const entries = [];
|
|
6900
|
+
visit(value, (_key, item) => {
|
|
6901
|
+
if (!isRecord8(item) || !Array.isArray(item.versions)) {
|
|
6902
|
+
return;
|
|
6797
6903
|
}
|
|
6904
|
+
const versionId = activeVersionId(item.versions);
|
|
6905
|
+
if (!versionId) {
|
|
6906
|
+
return;
|
|
6907
|
+
}
|
|
6908
|
+
const createdOn = typeof item.created_on === "string" ? Date.parse(item.created_on) : Number.NaN;
|
|
6909
|
+
const message = isRecord8(item.annotations) ? item.annotations["workers/message"] : void 0;
|
|
6910
|
+
entries.push({
|
|
6911
|
+
versionId,
|
|
6912
|
+
message: typeof message === "string" ? message : null,
|
|
6913
|
+
createdOn: Number.isNaN(createdOn) ? 0 : createdOn,
|
|
6914
|
+
index: entries.length
|
|
6915
|
+
});
|
|
6798
6916
|
});
|
|
6799
|
-
return
|
|
6917
|
+
return entries.sort((a, b) => b.createdOn - a.createdOn || b.index - a.index).map(({ versionId, message }) => ({ versionId, message }));
|
|
6800
6918
|
}
|
|
6801
|
-
function
|
|
6802
|
-
const
|
|
6803
|
-
|
|
6919
|
+
function activeVersionId(versions) {
|
|
6920
|
+
const records = versions.filter(isRecord8);
|
|
6921
|
+
const active = records.find((version) => version.percentage === 100) ?? records[0];
|
|
6922
|
+
const id = active?.version_id ?? active?.versionId;
|
|
6923
|
+
return typeof id === "string" ? id : null;
|
|
6804
6924
|
}
|
|
6805
|
-
function
|
|
6806
|
-
|
|
6807
|
-
visit(value, (_key, item) => {
|
|
6808
|
-
if (!digestValue && typeof item === "string") {
|
|
6809
|
-
const match = item.match(/moodle-cli-release:([a-zA-Z0-9._-]+)/u);
|
|
6810
|
-
if (match?.[1]) {
|
|
6811
|
-
digestValue = match[1];
|
|
6812
|
-
}
|
|
6813
|
-
}
|
|
6814
|
-
});
|
|
6815
|
-
return digestValue;
|
|
6925
|
+
function releaseDigestFromMessage(message) {
|
|
6926
|
+
return message?.match(/moodle-cli-release:([a-zA-Z0-9._-]+)/u)?.[1] ?? null;
|
|
6816
6927
|
}
|
|
6817
6928
|
function collectAccountObjects(value) {
|
|
6818
6929
|
const accounts = [];
|
|
@@ -7744,8 +7855,7 @@ var DefaultMcpCommandService = class {
|
|
|
7744
7855
|
this.worker = options.worker ?? new FetchManagedWorkerClient(options.fetchImpl);
|
|
7745
7856
|
this.renewal = options.renewal ?? new DefaultRenewalIntegration({
|
|
7746
7857
|
platform: process.platform,
|
|
7747
|
-
homeDirectory: this.homeDirectory
|
|
7748
|
-
executable: process.argv[1] ?? process.execPath
|
|
7858
|
+
homeDirectory: this.homeDirectory
|
|
7749
7859
|
});
|
|
7750
7860
|
this.sessions = options.sessions ?? createBackgroundMoodleSessionSource({
|
|
7751
7861
|
env: options.env,
|
|
@@ -7908,7 +8018,6 @@ var DefaultMcpCommandService = class {
|
|
|
7908
8018
|
const connectors = createDefaultClientConnectors(profile, {
|
|
7909
8019
|
homeDirectory: this.homeDirectory,
|
|
7910
8020
|
platform: process.platform,
|
|
7911
|
-
command: process.argv[1] ?? "moodle",
|
|
7912
8021
|
mode: input.mode,
|
|
7913
8022
|
...input.mode === "remote" ? { endpoint, accessToken: credentials.mcpAccessToken } : {}
|
|
7914
8023
|
});
|
|
@@ -7982,10 +8091,9 @@ var DefaultMcpCommandService = class {
|
|
|
7982
8091
|
throw new UsageError(`No managed Moodle MCP deployment exists for profile ${profile}.`);
|
|
7983
8092
|
}
|
|
7984
8093
|
let receipt = storedReceipt;
|
|
7985
|
-
const
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
});
|
|
8094
|
+
const target = { endpoint: receipt.productionEndpoint, sessionSyncToken: credentials.sessionSyncToken };
|
|
8095
|
+
await this.worker.touchSession(target);
|
|
8096
|
+
const readiness = await this.worker.getReadiness(target);
|
|
7989
8097
|
if (readiness.revision !== null && readiness.revision !== receipt.sessionRevision) {
|
|
7990
8098
|
receipt = await this.writeRenewalRevision(receipt, readiness.revision);
|
|
7991
8099
|
}
|
|
@@ -7997,6 +8105,7 @@ var DefaultMcpCommandService = class {
|
|
|
7997
8105
|
agentInstalled: await this.renewal.inspect(profile)
|
|
7998
8106
|
};
|
|
7999
8107
|
let replacement = null;
|
|
8108
|
+
let signInDetail;
|
|
8000
8109
|
if (snapshot.remote === "expiring" || snapshot.remote === "expired") {
|
|
8001
8110
|
try {
|
|
8002
8111
|
replacement = await this.sessions.loadValidated(profile, receipt.moodleOrigin);
|
|
@@ -8006,6 +8115,7 @@ var DefaultMcpCommandService = class {
|
|
|
8006
8115
|
throw error;
|
|
8007
8116
|
}
|
|
8008
8117
|
snapshot.replacement = { source: "mfa_required" };
|
|
8118
|
+
signInDetail = [error.message, error.hint].filter(Boolean).join(" ");
|
|
8009
8119
|
}
|
|
8010
8120
|
}
|
|
8011
8121
|
let uploaded = false;
|
|
@@ -8058,9 +8168,10 @@ var DefaultMcpCommandService = class {
|
|
|
8058
8168
|
text: "Moodle MCP session renewed."
|
|
8059
8169
|
};
|
|
8060
8170
|
}
|
|
8171
|
+
const detail = decision.state === "needs_sign_in" && signInDetail ? { detail: signInDetail } : {};
|
|
8061
8172
|
return {
|
|
8062
|
-
data: { profile, state: decision.state, reasonCode: decision.reasonCode, revision: receipt.sessionRevision },
|
|
8063
|
-
text: renewalResultText(decision)
|
|
8173
|
+
data: { profile, state: decision.state, reasonCode: decision.reasonCode, revision: receipt.sessionRevision, ...detail },
|
|
8174
|
+
text: [renewalResultText(decision), signInDetail].filter(Boolean).join("\n")
|
|
8064
8175
|
};
|
|
8065
8176
|
}
|
|
8066
8177
|
async writeRenewalRevision(receipt, revision) {
|
|
@@ -8111,7 +8222,6 @@ var DefaultMcpCommandService = class {
|
|
|
8111
8222
|
compatibilityDate: this.options.compatibilityDate ?? WORKER_COMPATIBILITY_DATE,
|
|
8112
8223
|
homeDirectory: this.homeDirectory,
|
|
8113
8224
|
platform: process.platform,
|
|
8114
|
-
executable: process.argv[1] ?? process.execPath,
|
|
8115
8225
|
fetch: this.options.fetchImpl,
|
|
8116
8226
|
auth: {
|
|
8117
8227
|
env: this.options.env,
|
|
@@ -8183,8 +8293,7 @@ Selection: `)).trim());
|
|
|
8183
8293
|
async connectedClientNames(profile) {
|
|
8184
8294
|
const connectors = createDefaultClientConnectors(profile, {
|
|
8185
8295
|
homeDirectory: this.homeDirectory,
|
|
8186
|
-
platform: process.platform
|
|
8187
|
-
command: process.argv[1] ?? "moodle"
|
|
8296
|
+
platform: process.platform
|
|
8188
8297
|
});
|
|
8189
8298
|
const clients = [];
|
|
8190
8299
|
for (const connector of connectors) {
|
package/dist/worker/worker.js
CHANGED
|
@@ -20597,7 +20597,7 @@ function stringValue(value) {
|
|
|
20597
20597
|
}
|
|
20598
20598
|
|
|
20599
20599
|
// src/version.ts
|
|
20600
|
-
var VERSION = "0.7.0-alpha.
|
|
20600
|
+
var VERSION = "0.7.0-alpha.7";
|
|
20601
20601
|
|
|
20602
20602
|
// src/worker/http.ts
|
|
20603
20603
|
var HEALTH_PATH = "/healthz";
|
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.7",
|
|
4
4
|
"description": "Terminal-first CLI for Moodle LMS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"vitest": "^3.2.4"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
|
-
"node": ">=22"
|
|
50
|
+
"node": ">=22.13"
|
|
51
51
|
},
|
|
52
52
|
"repository": {
|
|
53
53
|
"type": "git",
|