neuro-canvas-lms 2.10.0 → 2.11.0
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/AGENTS.md +4 -3
- package/installer/cli.mjs +46 -26
- package/installer/lib.mjs +10 -1
- package/installer/messages.mjs +39 -5
- package/installer/verify.mjs +61 -0
- package/package.json +2 -2
- package/version.mjs +1 -1
package/AGENTS.md
CHANGED
|
@@ -30,6 +30,7 @@ Code map:
|
|
|
30
30
|
- `owner.mjs`: owner binding, allowed OAuth clients. `i18n.mjs` + `pages.mjs`: web pages.
|
|
31
31
|
- `update-check.mjs`: once a day reads the newest version number from npm; when it is newer the AI
|
|
32
32
|
tells the user how to update. Also the `canvas_connector_version` tool.
|
|
33
|
-
- `installer/`: the setup wizard
|
|
34
|
-
settings
|
|
35
|
-
|
|
33
|
+
- `installer/`: the setup wizard. Re-running it asks once (y/n), then updates with the saved
|
|
34
|
+
settings (without a terminal it does not ask). `installer/verify.mjs`: the post-deploy online
|
|
35
|
+
check, also run alone by `npx -y neuro-canvas-lms check`.
|
|
36
|
+
- `version.mjs`: shown on the home page and /healthz, compared with npm by update-check.mjs.
|
package/installer/cli.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// Neuro-CanvasLMS installer. Deploys THIS copy of the code to your own Cloudflare account.
|
|
2
2
|
// npx -y neuro-canvas-lms install, or update an existing install
|
|
3
3
|
// npx -y neuro-canvas-lms uninstall remove it
|
|
4
|
+
// npx -y neuro-canvas-lms check only check an installed connector (deploys nothing)
|
|
4
5
|
// (from a clone: npm run setup / npm run setup -- uninstall)
|
|
5
6
|
// Running it again updates the connector to the code you are running and keeps its storage, so
|
|
6
7
|
// existing Claude/ChatGPT connections keep working.
|
|
7
8
|
// Language: your system language if supported (en, zh, es, ja, hi), else English; --lang to choose.
|
|
8
9
|
// Automation/tests: --name, --canvas-host, --school, --account-id, --yes; env NCLMS_HOME, NCLMS_LANG.
|
|
10
|
+
// Exit codes: 0 done, 1 failed, 2 deployed but not confirmed online yet (run `check` later).
|
|
9
11
|
import { spawnSync } from 'node:child_process';
|
|
10
12
|
import { createRequire } from 'node:module';
|
|
11
13
|
import fs from 'node:fs';
|
|
@@ -32,6 +34,7 @@ import {
|
|
|
32
34
|
subdomainLink,
|
|
33
35
|
} from './lib.mjs';
|
|
34
36
|
import { msg } from './messages.mjs';
|
|
37
|
+
import { verifyDeployment } from './verify.mjs';
|
|
35
38
|
|
|
36
39
|
const ROOT = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
|
|
37
40
|
// wrangler's package "exports" hide bin/; find the package root from its main entry instead.
|
|
@@ -227,27 +230,6 @@ async function runningVersion(url) {
|
|
|
227
230
|
}
|
|
228
231
|
}
|
|
229
232
|
|
|
230
|
-
async function healthCheck(url) {
|
|
231
|
-
let detail = 'no answer';
|
|
232
|
-
for (let i = 0; i < 20; i++) {
|
|
233
|
-
if (i) await new Promise(r => setTimeout(r, 3000));
|
|
234
|
-
try {
|
|
235
|
-
const h = await fetch(`${url}/healthz`, { headers: { 'Cache-Control': 'no-cache' } });
|
|
236
|
-
const j = h.ok ? await h.json() : null;
|
|
237
|
-
if (!j?.ok) detail = `/healthz HTTP ${h.status}`;
|
|
238
|
-
else if (j.version !== VERSION) detail = `still ${j.version}`;
|
|
239
|
-
else {
|
|
240
|
-
const mcp = await fetch(`${url}/mcp`, { method: 'POST', body: '{}' });
|
|
241
|
-
if (mcp.status === 401) return { ok: true, connected: j.connected === true };
|
|
242
|
-
detail = `/mcp HTTP ${mcp.status}`;
|
|
243
|
-
}
|
|
244
|
-
} catch (e) {
|
|
245
|
-
detail = e.message;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return { ok: false, detail };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
233
|
async function install() {
|
|
252
234
|
console.log(`\n ${msg(lang, 'title')}\n`);
|
|
253
235
|
const [major] = process.versions.node.split('.').map(Number);
|
|
@@ -302,8 +284,8 @@ async function install() {
|
|
|
302
284
|
save({ url, version: VERSION });
|
|
303
285
|
|
|
304
286
|
say('check');
|
|
305
|
-
const check = await
|
|
306
|
-
if (!check.ok)
|
|
287
|
+
const check = await verifyDeployment(url, VERSION, { onWarmup: () => say('cert_wait') });
|
|
288
|
+
if (!check.ok) return notConfirmed(name, url, check.detail);
|
|
307
289
|
ok('check_ok', { version: VERSION });
|
|
308
290
|
|
|
309
291
|
// Already connected: an update needs nothing from the user, so don't send them to make a token,
|
|
@@ -316,6 +298,22 @@ async function install() {
|
|
|
316
298
|
console.log('');
|
|
317
299
|
return;
|
|
318
300
|
}
|
|
301
|
+
nextSteps(url, host);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// The deploy worked but the address did not answer correctly in time (usually a brand-new
|
|
305
|
+
// workers.dev address still getting its certificate). Not a failure: say where it is and how to
|
|
306
|
+
// check again without deploying. Exit code 2 tells scripts and agents exactly that.
|
|
307
|
+
function notConfirmed(name, url, detail) {
|
|
308
|
+
const cmd = 'npx -y neuro-canvas-lms check' + (name === DEFAULT_NAME ? '' : ` --name ${name}`);
|
|
309
|
+
console.log('');
|
|
310
|
+
say('check_pending', { url, detail, cmd });
|
|
311
|
+
console.log('');
|
|
312
|
+
process.exitCode = 2;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// What the user does next, once the connector is online and not yet connected to an AI app.
|
|
316
|
+
function nextSteps(url, host) {
|
|
319
317
|
copy(`${url}/mcp`);
|
|
320
318
|
console.log(`\n ${msg(lang, 'done')}`);
|
|
321
319
|
say('done_1', { canvas: `https://${host}` });
|
|
@@ -330,6 +328,28 @@ async function install() {
|
|
|
330
328
|
if (!args.yes) openUrl(`https://${host}/profile/settings`);
|
|
331
329
|
}
|
|
332
330
|
|
|
331
|
+
// Only check an installed connector (after "not confirmed online yet"): nothing is deployed.
|
|
332
|
+
async function check() {
|
|
333
|
+
console.log(`\n ${msg(lang, 'title')}\n`);
|
|
334
|
+
const name = args.name ?? DEFAULT_NAME;
|
|
335
|
+
if (!validWorkerName(name)) fail('invalid --name');
|
|
336
|
+
const statePath = path.join(HOME, name, 'state.json');
|
|
337
|
+
const state = fs.existsSync(statePath) ? JSON.parse(fs.readFileSync(statePath, 'utf8')) : {};
|
|
338
|
+
if (!state.url) {
|
|
339
|
+
console.error(' ✗ ' + msg(lang, 'check_none', { name }));
|
|
340
|
+
process.exitCode = 1;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
say('check_only', { url: state.url });
|
|
344
|
+
const result = await verifyDeployment(state.url, state.version, {
|
|
345
|
+
onWarmup: () => say('cert_wait'),
|
|
346
|
+
});
|
|
347
|
+
if (!result.ok) return notConfirmed(name, state.url, result.detail);
|
|
348
|
+
ok('check_ok', { version: result.version });
|
|
349
|
+
if (result.connected) return ok('check_connected');
|
|
350
|
+
nextSteps(state.url, state.host);
|
|
351
|
+
}
|
|
352
|
+
|
|
333
353
|
async function uninstall() {
|
|
334
354
|
const name = args.name ?? DEFAULT_NAME;
|
|
335
355
|
const dir = path.join(HOME, name);
|
|
@@ -369,11 +389,11 @@ const help = async () =>
|
|
|
369
389
|
fs
|
|
370
390
|
.readFileSync(fileURLToPath(import.meta.url), 'utf8')
|
|
371
391
|
.split('\n')
|
|
372
|
-
.slice(0,
|
|
392
|
+
.slice(0, 10)
|
|
373
393
|
.join('\n'),
|
|
374
394
|
);
|
|
375
|
-
({ install, uninstall, help })[args.command]().then(
|
|
376
|
-
() => process.exit(0),
|
|
395
|
+
({ install, uninstall, help, check })[args.command]().then(
|
|
396
|
+
() => process.exit(process.exitCode ?? 0),
|
|
377
397
|
e => {
|
|
378
398
|
console.error('\n ✗ ' + msg(lang, 'failed', { detail: e?.message ?? e }) + '\n');
|
|
379
399
|
process.exit(1);
|
package/installer/lib.mjs
CHANGED
|
@@ -68,7 +68,7 @@ export function parseArgs(argv) {
|
|
|
68
68
|
const out = { command: 'install' };
|
|
69
69
|
for (let i = 0; i < argv.length; i++) {
|
|
70
70
|
const a = argv[i];
|
|
71
|
-
if (
|
|
71
|
+
if (['install', 'uninstall', 'help', 'check'].includes(a)) out.command = a;
|
|
72
72
|
else if (a === '--yes' || a === '-y') out.yes = true;
|
|
73
73
|
else if (a.startsWith('--')) {
|
|
74
74
|
const [k, v] = a.includes('=') ? a.slice(2).split(/=(.*)/s) : [a.slice(2), argv[++i]];
|
|
@@ -108,3 +108,12 @@ export const isYes = answer => /^(?:y|yes)$/i.test(String(answer ?? '').trim());
|
|
|
108
108
|
export const needsSubdomain = out => /register a workers\.dev subdomain/i.test(out ?? '');
|
|
109
109
|
export const subdomainLink = accountId =>
|
|
110
110
|
`https://dash.cloudflare.com/${encodeURIComponent(accountId)}/workers/onboarding`;
|
|
111
|
+
|
|
112
|
+
// A new workers.dev address answers only once its DNS and HTTPS certificate are ready, which can
|
|
113
|
+
// take minutes on a brand-new subdomain. These errors mean "not ready yet", not "broken".
|
|
114
|
+
export const isWarmingUp = detail =>
|
|
115
|
+
/SSL|TLS|CERT|UNABLE_TO_VERIFY|SELF_SIGNED|EPROTO|handshake|ENOTFOUND|EAI_AGAIN|ECONNRESET|ECONNREFUSED|certificate/i.test(
|
|
116
|
+
detail ?? '',
|
|
117
|
+
);
|
|
118
|
+
export const CHECK_WAIT = 60 * 1000; // a normal deploy answers within a minute
|
|
119
|
+
export const WARMUP_WAIT = 5 * 60 * 1000; // a new address may need a few minutes for its certificate
|
package/installer/messages.mjs
CHANGED
|
@@ -23,7 +23,6 @@ const M = {
|
|
|
23
23
|
deploy_ok: 'Connector is live: {url}',
|
|
24
24
|
check: 'Step 4/4 · Checking the connector…',
|
|
25
25
|
check_ok: 'Everything works (version {version}).',
|
|
26
|
-
check_bad: 'The connector did not pass its check: {detail}',
|
|
27
26
|
done: 'Done! Last step, in your AI:',
|
|
28
27
|
done_1:
|
|
29
28
|
'1. Create a Canvas access token: {canvas}/profile/settings → "+ New Access Token" (copy it; Canvas shows it once).',
|
|
@@ -47,6 +46,14 @@ const M = {
|
|
|
47
46
|
un_done:
|
|
48
47
|
'Removed. Also delete the Canvas token (Canvas → Account → Settings) and the connector in your AI app.',
|
|
49
48
|
cancelled: 'Cancelled.',
|
|
49
|
+
check_pending:
|
|
50
|
+
'Deployed to {url}, but it could not be confirmed online yet ({detail}). A new address can take a few more minutes the first time. Nothing needs to be deployed again: in a few minutes, check it with\n {cmd}',
|
|
51
|
+
check_none:
|
|
52
|
+
'No install named "{name}" was found (installed under another name? add --name <name>). Install it first with npx -y neuro-canvas-lms.',
|
|
53
|
+
check_only: 'Checking the connector at {url}…',
|
|
54
|
+
check_connected: 'It is connected to your AI apps and ready to use.',
|
|
55
|
+
cert_wait:
|
|
56
|
+
'The new workers.dev address is still getting its HTTPS certificate. The first time this can take a few minutes; waiting…',
|
|
50
57
|
pick_account_flag:
|
|
51
58
|
'This Cloudflare login has several accounts. Run the same command again with one of these added:\n{list}',
|
|
52
59
|
canvas_flag:
|
|
@@ -87,7 +94,6 @@ const M = {
|
|
|
87
94
|
deploy_ok: '连接器已上线:{url}',
|
|
88
95
|
check: '第 4/4 步 · 检查连接器…',
|
|
89
96
|
check_ok: '一切正常(版本 {version})。',
|
|
90
|
-
check_bad: '连接器没通过检查:{detail}',
|
|
91
97
|
done: '装好了!最后一步,在你的 AI 里:',
|
|
92
98
|
done_1:
|
|
93
99
|
'1. 生成 Canvas 访问令牌:{canvas}/profile/settings →「+ 新建访问许可证」(立刻复制,Canvas 只显示一次)。',
|
|
@@ -105,6 +111,13 @@ const M = {
|
|
|
105
111
|
un_failed: '没能删除{what}:{detail}\n 本地安装记录已保留,再运行一次同一条命令即可重试。',
|
|
106
112
|
un_done: '已删除。另外请删掉 Canvas 令牌(Canvas → 账户 → 设置)和 AI 应用里的连接器。',
|
|
107
113
|
cancelled: '已取消。',
|
|
114
|
+
check_pending:
|
|
115
|
+
'已部署到 {url},但还没能确认它已上线({detail})。新地址第一次可能还要几分钟。不需要重新部署:过几分钟用这条命令只做检查:\n {cmd}',
|
|
116
|
+
check_none:
|
|
117
|
+
'没找到名为「{name}」的安装(装的时候用了别的名字?加上 --name <名字>)。请先用 npx -y neuro-canvas-lms 安装。',
|
|
118
|
+
check_only: '检查连接器 {url}…',
|
|
119
|
+
check_connected: '已经连接到你的 AI 应用,可以直接用。',
|
|
120
|
+
cert_wait: '新的 workers.dev 地址还在申请 HTTPS 证书,第一次可能要几分钟,正在等待…',
|
|
108
121
|
pick_account_flag:
|
|
109
122
|
'这个 Cloudflare 登录下有多个账号。请在同一条命令后加上其中一个,再运行一次:\n{list}',
|
|
110
123
|
canvas_flag:
|
|
@@ -147,7 +160,6 @@ const M = {
|
|
|
147
160
|
deploy_ok: 'El conector está activo: {url}',
|
|
148
161
|
check: 'Paso 4/4 · Comprobando el conector…',
|
|
149
162
|
check_ok: 'Todo funciona (versión {version}).',
|
|
150
|
-
check_bad: 'El conector no pasó la comprobación: {detail}',
|
|
151
163
|
done: '¡Listo! Último paso, en tu IA:',
|
|
152
164
|
done_1:
|
|
153
165
|
'1. Crea un token de acceso de Canvas: {canvas}/profile/settings → "+ Nuevo token de acceso" (cópialo; Canvas lo muestra una sola vez).',
|
|
@@ -172,6 +184,14 @@ const M = {
|
|
|
172
184
|
un_done:
|
|
173
185
|
'Eliminado. Borra también el token de Canvas (Canvas → Cuenta → Configuración) y el conector en tu app de IA.',
|
|
174
186
|
cancelled: 'Cancelado.',
|
|
187
|
+
check_pending:
|
|
188
|
+
'Desplegado en {url}, pero aún no se pudo confirmar que está en línea ({detail}). Una dirección nueva puede tardar unos minutos más la primera vez. No hace falta volver a desplegar: dentro de unos minutos, compruébalo con\n {cmd}',
|
|
189
|
+
check_none:
|
|
190
|
+
'No se encontró ninguna instalación llamada "{name}" (¿la instalaste con otro nombre? añade --name <nombre>). Instálala primero con npx -y neuro-canvas-lms.',
|
|
191
|
+
check_only: 'Comprobando el conector en {url}…',
|
|
192
|
+
check_connected: 'Está conectado a tus apps de IA y listo para usar.',
|
|
193
|
+
cert_wait:
|
|
194
|
+
'La nueva dirección workers.dev todavía está obteniendo su certificado HTTPS. La primera vez puede tardar unos minutos; esperando…',
|
|
175
195
|
pick_account_flag:
|
|
176
196
|
'Este inicio de sesión de Cloudflare tiene varias cuentas. Ejecuta el mismo comando otra vez añadiendo una de estas:\n{list}',
|
|
177
197
|
canvas_flag:
|
|
@@ -215,7 +235,6 @@ const M = {
|
|
|
215
235
|
deploy_ok: 'コネクタが公開されました:{url}',
|
|
216
236
|
check: 'ステップ 4/4 · コネクタを確認しています…',
|
|
217
237
|
check_ok: 'すべて正常です(バージョン {version})。',
|
|
218
|
-
check_bad: 'コネクタが確認に失敗しました:{detail}',
|
|
219
238
|
done: '完了!最後のステップ(AI 側):',
|
|
220
239
|
done_1:
|
|
221
240
|
'1. Canvas アクセストークンを作成:{canvas}/profile/settings →「+ 新しいアクセストークン」(すぐコピー。表示は一度きりです)。',
|
|
@@ -239,6 +258,14 @@ const M = {
|
|
|
239
258
|
un_done:
|
|
240
259
|
'削除しました。Canvas トークン(Canvas → アカウント → 設定)と AI アプリのコネクタも削除してください。',
|
|
241
260
|
cancelled: 'キャンセルしました。',
|
|
261
|
+
check_pending:
|
|
262
|
+
'{url} へのデプロイは完了しましたが、まだオンラインであることを確認できていません({detail})。新しいアドレスは初回にさらに数分かかることがあります。再デプロイは不要です。数分後に次のコマンドで確認だけ行ってください:\n {cmd}',
|
|
263
|
+
check_none:
|
|
264
|
+
'「{name}」という名前のインストールが見つかりません(別の名前でインストールした場合は --name <名前> を付けてください)。先に npx -y neuro-canvas-lms でインストールしてください。',
|
|
265
|
+
check_only: '{url} のコネクタを確認しています…',
|
|
266
|
+
check_connected: 'AI アプリに接続済みで、すぐに使えます。',
|
|
267
|
+
cert_wait:
|
|
268
|
+
'新しい workers.dev アドレスの HTTPS 証明書を準備しています。初回は数分かかることがあります。待機しています…',
|
|
242
269
|
pick_account_flag:
|
|
243
270
|
'この Cloudflare ログインには複数のアカウントがあります。同じコマンドに次のどれかを付けて、もう一度実行してください:\n{list}',
|
|
244
271
|
canvas_flag:
|
|
@@ -282,7 +309,6 @@ const M = {
|
|
|
282
309
|
deploy_ok: 'कनेक्टर चालू है: {url}',
|
|
283
310
|
check: 'चरण 4/4 · कनेक्टर की जाँच हो रही है…',
|
|
284
311
|
check_ok: 'सब कुछ ठीक है (संस्करण {version})।',
|
|
285
|
-
check_bad: 'कनेक्टर जाँच में विफल रहा: {detail}',
|
|
286
312
|
done: 'हो गया! आख़िरी कदम, अपने AI में:',
|
|
287
313
|
done_1:
|
|
288
314
|
'1. Canvas एक्सेस टोकन बनाएँ: {canvas}/profile/settings → "+ New Access Token" (तुरंत कॉपी करें; Canvas इसे केवल एक बार दिखाता है)।',
|
|
@@ -306,6 +332,14 @@ const M = {
|
|
|
306
332
|
un_done:
|
|
307
333
|
'हटा दिया गया। Canvas टोकन (Canvas → Account → Settings) और अपने AI ऐप में कनेक्टर भी हटाएँ।',
|
|
308
334
|
cancelled: 'रद्द किया गया।',
|
|
335
|
+
check_pending:
|
|
336
|
+
'{url} पर डिप्लॉय हो गया, लेकिन अभी पुष्टि नहीं हो पाई कि यह ऑनलाइन है ({detail})। नए पते को पहली बार कुछ और मिनट लग सकते हैं। दोबारा डिप्लॉय करने की ज़रूरत नहीं: कुछ मिनट बाद इस कमांड से सिर्फ़ जाँच करें:\n {cmd}',
|
|
337
|
+
check_none:
|
|
338
|
+
'"{name}" नाम का कोई इंस्टॉल नहीं मिला (किसी और नाम से इंस्टॉल किया था? --name <नाम> जोड़ें)। पहले npx -y neuro-canvas-lms से इंस्टॉल करें।',
|
|
339
|
+
check_only: '{url} पर कनेक्टर की जाँच हो रही है…',
|
|
340
|
+
check_connected: 'यह आपके AI ऐप से जुड़ा है और इस्तेमाल के लिए तैयार है।',
|
|
341
|
+
cert_wait:
|
|
342
|
+
'नए workers.dev पते का HTTPS प्रमाणपत्र अभी तैयार हो रहा है। पहली बार इसमें कुछ मिनट लग सकते हैं; प्रतीक्षा हो रही है…',
|
|
309
343
|
pick_account_flag:
|
|
310
344
|
'इस Cloudflare लॉगिन में कई खाते हैं। वही कमांड इनमें से एक जोड़कर फिर से चलाएँ:\n{list}',
|
|
311
345
|
canvas_flag:
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Is a freshly deployed connector online and running the expected version?
|
|
2
|
+
// Ready means: /healthz answers ok with that version AND /mcp refuses an unauthenticated request
|
|
3
|
+
// with 401 (the OAuth layer is in front). Each request has its own timeout and the whole check a
|
|
4
|
+
// deadline, so it always ends. A new workers.dev address can take minutes for its DNS and HTTPS
|
|
5
|
+
// certificate: for those "not ready yet" errors the deadline is longer. Everything that touches
|
|
6
|
+
// the network or the clock can be replaced, so the behaviour is tested (verify.test.mjs).
|
|
7
|
+
import { isWarmingUp, CHECK_WAIT, WARMUP_WAIT } from './lib.mjs';
|
|
8
|
+
|
|
9
|
+
// fetch reports only "fetch failed"; the cause names the real problem (a TLS alert, DNS, …).
|
|
10
|
+
export function errorDetail(e) {
|
|
11
|
+
if (e?.name === 'TimeoutError' || e?.cause?.name === 'TimeoutError') return 'no answer (timeout)';
|
|
12
|
+
return String(e?.cause?.code ?? e?.cause?.message ?? e?.message ?? e);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function verifyDeployment(
|
|
16
|
+
url,
|
|
17
|
+
version,
|
|
18
|
+
{
|
|
19
|
+
fetcher = fetch,
|
|
20
|
+
sleep = ms => new Promise(r => setTimeout(r, ms)),
|
|
21
|
+
now = Date.now,
|
|
22
|
+
requestTimeout = 10 * 1000,
|
|
23
|
+
interval = 3 * 1000,
|
|
24
|
+
normalWait = CHECK_WAIT,
|
|
25
|
+
warmupWait = WARMUP_WAIT,
|
|
26
|
+
onWarmup = () => {},
|
|
27
|
+
} = {},
|
|
28
|
+
) {
|
|
29
|
+
let detail = 'no answer';
|
|
30
|
+
let told = false;
|
|
31
|
+
const start = now();
|
|
32
|
+
for (let i = 0; now() - start < (isWarmingUp(detail) ? warmupWait : normalWait); i++) {
|
|
33
|
+
if (i) await sleep(interval);
|
|
34
|
+
try {
|
|
35
|
+
const h = await fetcher(`${url}/healthz`, {
|
|
36
|
+
headers: { 'Cache-Control': 'no-cache' },
|
|
37
|
+
signal: AbortSignal.timeout(requestTimeout),
|
|
38
|
+
});
|
|
39
|
+
const j = h.ok ? await h.json() : null;
|
|
40
|
+
if (!j?.ok) detail = `/healthz HTTP ${h.status}`;
|
|
41
|
+
else if (version && j.version !== version) detail = `still version ${j.version}`;
|
|
42
|
+
else {
|
|
43
|
+
const mcp = await fetcher(`${url}/mcp`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
body: '{}',
|
|
46
|
+
signal: AbortSignal.timeout(requestTimeout),
|
|
47
|
+
});
|
|
48
|
+
if (mcp.status === 401)
|
|
49
|
+
return { ok: true, version: j.version, connected: j.connected === true };
|
|
50
|
+
detail = `/mcp HTTP ${mcp.status} (expected 401)`;
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
detail = errorDetail(e);
|
|
54
|
+
if (isWarmingUp(detail) && !told) {
|
|
55
|
+
told = true;
|
|
56
|
+
onWarmup(detail);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { ok: false, detail };
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuro-canvas-lms",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Neuro-CanvasLMS: read-only connector that lets Claude or ChatGPT read any Canvas LMS (remote MCP server on your own Cloudflare account)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"canvas",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "https://github.com/BloodyReginaa/Neuro-CanvasLMS.git"
|
|
23
|
+
"url": "git+https://github.com/BloodyReginaa/Neuro-CanvasLMS.git"
|
|
24
24
|
},
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"author": "BloodyReginaa",
|
package/version.mjs
CHANGED