infinitecampus-mcp 2.3.5 → 2.3.6
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +361 -145
- package/dist/client.js +161 -114
- package/dist/index.js +1 -1
- package/dist/tools/_shared.js +2 -11
- package/dist/tools/assessments.js +3 -2
- package/dist/tools/assignments.js +2 -2
- package/dist/tools/attendance.js +3 -2
- package/dist/tools/attendance_events.js +3 -2
- package/dist/tools/behavior.js +3 -2
- package/dist/tools/calendar.js +3 -2
- package/dist/tools/districts.js +2 -2
- package/dist/tools/documents.js +5 -4
- package/dist/tools/features.js +3 -2
- package/dist/tools/fees.js +3 -2
- package/dist/tools/foodservice.js +3 -2
- package/dist/tools/grades.js +2 -2
- package/dist/tools/messages.js +4 -3
- package/dist/tools/recent_grades.js +2 -2
- package/dist/tools/schedule.js +2 -2
- package/dist/tools/students.js +2 -2
- package/dist/tools/teachers.js +3 -2
- package/package.json +2 -2
- package/server.json +2 -2
package/dist/bundle.js
CHANGED
|
@@ -34704,6 +34704,9 @@ var API_KEY_RE = new RegExp([
|
|
|
34704
34704
|
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
34705
34705
|
// webhook signing secret (Stripe-style)
|
|
34706
34706
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
34707
|
+
var JSON_SECRET_KEYS = "access_token|refresh_token|client_secret|api_?key|password|passwd|secret|token";
|
|
34708
|
+
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")[^"]*(")`, "gi");
|
|
34709
|
+
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*(')`, "gi");
|
|
34707
34710
|
|
|
34708
34711
|
// node_modules/@chrischall/mcp-utils/dist/response/index.js
|
|
34709
34712
|
function textResult(data) {
|
|
@@ -38188,7 +38191,7 @@ function loadAccount(env = process.env) {
|
|
|
38188
38191
|
// package.json
|
|
38189
38192
|
var package_default = {
|
|
38190
38193
|
name: "infinitecampus-mcp",
|
|
38191
|
-
version: "2.3.
|
|
38194
|
+
version: "2.3.6",
|
|
38192
38195
|
mcpName: "io.github.chrischall/infinitecampus-mcp",
|
|
38193
38196
|
description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
|
|
38194
38197
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -38230,7 +38233,7 @@ var package_default = {
|
|
|
38230
38233
|
"test:watch": "vitest"
|
|
38231
38234
|
},
|
|
38232
38235
|
dependencies: {
|
|
38233
|
-
"@chrischall/mcp-utils": "^0.
|
|
38236
|
+
"@chrischall/mcp-utils": "^0.12.0",
|
|
38234
38237
|
"@fetchproxy/bootstrap": "^1.0.0",
|
|
38235
38238
|
"@fetchproxy/server": "^1.0.0",
|
|
38236
38239
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -38318,10 +38321,163 @@ async function resolveAuth() {
|
|
|
38318
38321
|
// src/client.ts
|
|
38319
38322
|
import { writeFile as writeFile2, stat } from "fs/promises";
|
|
38320
38323
|
import { dirname } from "path";
|
|
38324
|
+
|
|
38325
|
+
// node_modules/@chrischall/mcp-utils/dist/session/index.js
|
|
38326
|
+
var TOKEN_REFRESH_SKEW_MS = 5 * 60 * 1e3;
|
|
38327
|
+
var CookieSessionManager = class {
|
|
38328
|
+
session;
|
|
38329
|
+
inFlight;
|
|
38330
|
+
/** A cached *permanent* config error: once set, every `ensure` rethrows it. */
|
|
38331
|
+
permanentError;
|
|
38332
|
+
/** When the current session was minted (login) or installed (seed) — for maxAgeMs. */
|
|
38333
|
+
sessionAt = 0;
|
|
38334
|
+
loginFn;
|
|
38335
|
+
isExpiredFn;
|
|
38336
|
+
isPermanentErrorFn;
|
|
38337
|
+
maxAgeMs;
|
|
38338
|
+
now;
|
|
38339
|
+
onReplayLoginErrorFn;
|
|
38340
|
+
constructor(opts) {
|
|
38341
|
+
this.loginFn = opts.login;
|
|
38342
|
+
this.isExpiredFn = opts.isExpired ?? (() => false);
|
|
38343
|
+
this.isPermanentErrorFn = opts.isPermanentError ?? (() => false);
|
|
38344
|
+
this.maxAgeMs = opts.maxAgeMs;
|
|
38345
|
+
this.now = opts.now ?? Date.now;
|
|
38346
|
+
this.onReplayLoginErrorFn = opts.onReplayLoginError;
|
|
38347
|
+
}
|
|
38348
|
+
/** The current session, or `undefined` before the first successful login. */
|
|
38349
|
+
get current() {
|
|
38350
|
+
return this.session;
|
|
38351
|
+
}
|
|
38352
|
+
/** True when {@link CookieSessionManagerOptions.maxAgeMs} says the session is too old. */
|
|
38353
|
+
isStale() {
|
|
38354
|
+
return this.maxAgeMs !== void 0 && this.now() - this.sessionAt >= this.maxAgeMs;
|
|
38355
|
+
}
|
|
38356
|
+
/**
|
|
38357
|
+
* Return the current session, or single-flight a login if there is none.
|
|
38358
|
+
*
|
|
38359
|
+
* Concurrent callers share one in-flight login; the in-flight promise is
|
|
38360
|
+
* cleared on settle (success OR failure) so a rejected login never sticks and
|
|
38361
|
+
* the next call retries. A login error classified permanent by
|
|
38362
|
+
* {@link CookieSessionManagerOptions.isPermanentError} is cached and rethrown
|
|
38363
|
+
* on every later call; a transient error is not cached (next call retries).
|
|
38364
|
+
* With {@link CookieSessionManagerOptions.maxAgeMs} set, a session past its
|
|
38365
|
+
* TTL is invalidated first, so the call falls through to a (single-flight)
|
|
38366
|
+
* re-login.
|
|
38367
|
+
*/
|
|
38368
|
+
async ensure() {
|
|
38369
|
+
if (this.session !== void 0) {
|
|
38370
|
+
if (!this.isStale())
|
|
38371
|
+
return this.session;
|
|
38372
|
+
this.invalidate();
|
|
38373
|
+
}
|
|
38374
|
+
if (this.permanentError !== void 0)
|
|
38375
|
+
throw this.permanentError;
|
|
38376
|
+
if (this.inFlight === void 0) {
|
|
38377
|
+
this.inFlight = this.runLogin();
|
|
38378
|
+
}
|
|
38379
|
+
return this.inFlight;
|
|
38380
|
+
}
|
|
38381
|
+
/**
|
|
38382
|
+
* Install an externally-minted session (e.g. infinitecampus's CUPS linked-
|
|
38383
|
+
* district discovery, which mints sessions outside {@link
|
|
38384
|
+
* CookieSessionManagerOptions.login}). The seed becomes the current session
|
|
38385
|
+
* with a fresh {@link CookieSessionManagerOptions.maxAgeMs} clock, and any
|
|
38386
|
+
* in-flight login is DETACHED: its waiters still receive its result, but a
|
|
38387
|
+
* late resolution will not overwrite the seed. A cached permanent config
|
|
38388
|
+
* error is left intact — the login path is still misconfigured, and the next
|
|
38389
|
+
* post-seed re-login should keep saying so.
|
|
38390
|
+
*/
|
|
38391
|
+
seed(session) {
|
|
38392
|
+
this.session = session;
|
|
38393
|
+
this.sessionAt = this.now();
|
|
38394
|
+
this.inFlight = void 0;
|
|
38395
|
+
}
|
|
38396
|
+
/**
|
|
38397
|
+
* One login attempt. Self-clears `inFlight` on settle so a rejected login
|
|
38398
|
+
* never sticks, and stamps the resulting session only while it's still the
|
|
38399
|
+
* live attempt (an `invalidate()` mid-flight cleared `inFlight` — a stale
|
|
38400
|
+
* resolution must not re-stamp the dropped session over the new state).
|
|
38401
|
+
*/
|
|
38402
|
+
runLogin() {
|
|
38403
|
+
const holder = {};
|
|
38404
|
+
holder.p = (async () => {
|
|
38405
|
+
try {
|
|
38406
|
+
const session = await this.loginFn();
|
|
38407
|
+
if (this.inFlight === holder.p) {
|
|
38408
|
+
this.session = session;
|
|
38409
|
+
this.sessionAt = this.now();
|
|
38410
|
+
}
|
|
38411
|
+
return session;
|
|
38412
|
+
} catch (err) {
|
|
38413
|
+
if (this.isPermanentErrorFn(err))
|
|
38414
|
+
this.permanentError = err;
|
|
38415
|
+
throw err;
|
|
38416
|
+
} finally {
|
|
38417
|
+
if (this.inFlight === holder.p)
|
|
38418
|
+
this.inFlight = void 0;
|
|
38419
|
+
}
|
|
38420
|
+
})();
|
|
38421
|
+
return holder.p;
|
|
38422
|
+
}
|
|
38423
|
+
/**
|
|
38424
|
+
* Drop the current session (and any in-flight login) so the next
|
|
38425
|
+
* {@link ensure} re-runs {@link CookieSessionManagerOptions.login}. Does NOT
|
|
38426
|
+
* clear a cached permanent config error — that only clears on a fresh process
|
|
38427
|
+
* (new config). Used to recover from a detected session expiry.
|
|
38428
|
+
*/
|
|
38429
|
+
invalidate() {
|
|
38430
|
+
this.session = void 0;
|
|
38431
|
+
this.inFlight = void 0;
|
|
38432
|
+
}
|
|
38433
|
+
/**
|
|
38434
|
+
* Run an authenticated `call` with the current session and reactive
|
|
38435
|
+
* expiry-replay. `call` receives the session and returns a `Response`. If
|
|
38436
|
+
* {@link CookieSessionManagerOptions.isExpired} flags the response, the
|
|
38437
|
+
* session is invalidated, a single-flight re-login runs, and `call` is
|
|
38438
|
+
* replayed EXACTLY once. A persistent expiry surfaces the (second) response
|
|
38439
|
+
* rather than looping. If the re-login itself fails, the original
|
|
38440
|
+
* (expired-looking) response is returned so the caller can surface a clean
|
|
38441
|
+
* sign-in error rather than the login failure.
|
|
38442
|
+
*
|
|
38443
|
+
* `call` resolves to `R` (the generic response type, default {@link Response});
|
|
38444
|
+
* the manager passes it untouched to {@link CookieSessionManagerOptions.isExpired}
|
|
38445
|
+
* and returns it untouched, so a custom transport type flows through cleanly.
|
|
38446
|
+
*/
|
|
38447
|
+
async withSession(call) {
|
|
38448
|
+
const session = await this.ensure();
|
|
38449
|
+
const res = await call(session);
|
|
38450
|
+
if (!await this.isExpiredFn(res))
|
|
38451
|
+
return res;
|
|
38452
|
+
if (this.current === session)
|
|
38453
|
+
this.invalidate();
|
|
38454
|
+
let fresh;
|
|
38455
|
+
try {
|
|
38456
|
+
fresh = await this.ensure();
|
|
38457
|
+
} catch (err) {
|
|
38458
|
+
this.onReplayLoginErrorFn?.(err);
|
|
38459
|
+
return res;
|
|
38460
|
+
}
|
|
38461
|
+
return call(fresh);
|
|
38462
|
+
}
|
|
38463
|
+
};
|
|
38464
|
+
function createCookieSessionManager(opts) {
|
|
38465
|
+
return new CookieSessionManager(opts);
|
|
38466
|
+
}
|
|
38467
|
+
|
|
38468
|
+
// src/client.ts
|
|
38321
38469
|
var SESSION_TTL_MS = 5 * 60 * 60 * 1e3;
|
|
38322
38470
|
var ICClient = class {
|
|
38323
38471
|
accounts = /* @__PURE__ */ new Map();
|
|
38324
|
-
|
|
38472
|
+
/**
|
|
38473
|
+
* One CookieSessionManager per district (primary + each discovered linked
|
|
38474
|
+
* district). The manager owns the session lifecycle: single-flight login,
|
|
38475
|
+
* `withSession` exactly-one 401 replay, `invalidate()`, permanent-error
|
|
38476
|
+
* caching, proactive `maxAgeMs` (SESSION_TTL_MS) expiry, and — for linked
|
|
38477
|
+
* districts — the CUPS-minted session installed via `seed()` during
|
|
38478
|
+
* discovery.
|
|
38479
|
+
*/
|
|
38480
|
+
managers = /* @__PURE__ */ new Map();
|
|
38325
38481
|
linkedTo = /* @__PURE__ */ new Map();
|
|
38326
38482
|
// linkedDistrictName → primaryDistrictName
|
|
38327
38483
|
primaryName;
|
|
@@ -38335,6 +38491,8 @@ var ICClient = class {
|
|
|
38335
38491
|
*/
|
|
38336
38492
|
fetchproxyMode = false;
|
|
38337
38493
|
fetchproxyDiscoveryRan = false;
|
|
38494
|
+
/** fetchproxy cookies, consumed by the primary manager's first login. */
|
|
38495
|
+
preloaded = null;
|
|
38338
38496
|
/**
|
|
38339
38497
|
* `preloaded` is the fetchproxy escape hatch: when set, the client treats
|
|
38340
38498
|
* the supplied cookies as a freshly-completed login on the primary account.
|
|
@@ -38347,21 +38505,66 @@ var ICClient = class {
|
|
|
38347
38505
|
constructor(account2, opts = {}) {
|
|
38348
38506
|
this.accounts.set(account2.name, account2);
|
|
38349
38507
|
this.primaryName = account2.name;
|
|
38508
|
+
this.managers.set(account2.name, this.makeManager(account2));
|
|
38350
38509
|
if (opts.preloaded) {
|
|
38351
|
-
this.
|
|
38352
|
-
cookie: opts.preloaded.cookieHeader,
|
|
38353
|
-
xsrfToken: opts.preloaded.xsrfToken,
|
|
38354
|
-
loggedInAt: Date.now(),
|
|
38355
|
-
loginInFlight: null
|
|
38356
|
-
});
|
|
38510
|
+
this.preloaded = opts.preloaded;
|
|
38357
38511
|
this.fetchproxyMode = true;
|
|
38358
38512
|
}
|
|
38359
38513
|
}
|
|
38514
|
+
/**
|
|
38515
|
+
* Build the per-district CookieSessionManager.
|
|
38516
|
+
*
|
|
38517
|
+
* - `maxAgeMs: SESSION_TTL_MS` makes `ensure()` proactively invalidate and
|
|
38518
|
+
* re-login a session older than 5h (single-flight), replacing IC's old
|
|
38519
|
+
* hand-rolled `ensureFresh` wrapper. `now` reads `Date.now` at call time so
|
|
38520
|
+
* the clock stays mockable in tests.
|
|
38521
|
+
* - `isPermanentError` caches the one config error the process can never
|
|
38522
|
+
* recover from (fetchproxy no-creds) so `ensure()` fails fast instead of
|
|
38523
|
+
* pointlessly retrying a login that can never succeed.
|
|
38524
|
+
* - `onReplayLoginError` rethrows the re-login failure from `withSession`'s
|
|
38525
|
+
* expiry-replay path. By default that failure is swallowed and the stale
|
|
38526
|
+
* 401 returned; rethrowing surfaces the actionable error (e.g. fetchproxy's
|
|
38527
|
+
* "sign back into your IC portal in the browser") to the caller instead of
|
|
38528
|
+
* a generic SessionExpiredError — the behavior IC's `lastLoginError`
|
|
38529
|
+
* bookkeeping used to provide.
|
|
38530
|
+
*/
|
|
38531
|
+
makeManager(account2) {
|
|
38532
|
+
return createCookieSessionManager({
|
|
38533
|
+
login: () => this.login(account2),
|
|
38534
|
+
isExpired: (res) => this.detectExpiredSession(account2.name, res),
|
|
38535
|
+
isPermanentError: (e) => e instanceof AuthFailedError && e.permanent,
|
|
38536
|
+
maxAgeMs: SESSION_TTL_MS,
|
|
38537
|
+
now: () => Date.now(),
|
|
38538
|
+
onReplayLoginError: (err) => {
|
|
38539
|
+
throw err;
|
|
38540
|
+
}
|
|
38541
|
+
});
|
|
38542
|
+
}
|
|
38543
|
+
/**
|
|
38544
|
+
* `isExpired` predicate for `withSession`: a 401 means the session died.
|
|
38545
|
+
* Side effect for linked districts: the shared CUPS SSO umbrella died with
|
|
38546
|
+
* it, so drop the primary and every *sibling* linked session too — the
|
|
38547
|
+
* linked district's re-login (via `login()`'s linked branch) then re-logs
|
|
38548
|
+
* the primary in, whose discovery re-establishes all of them. `withSession`
|
|
38549
|
+
* invalidates the requesting district itself. Mirrors the old hand-rolled
|
|
38550
|
+
* doRequest 401 handling.
|
|
38551
|
+
*/
|
|
38552
|
+
detectExpiredSession(district, res) {
|
|
38553
|
+
if (res.status !== 401) return false;
|
|
38554
|
+
const primaryName = this.linkedTo.get(district);
|
|
38555
|
+
if (primaryName) {
|
|
38556
|
+
this.managers.get(primaryName).invalidate();
|
|
38557
|
+
for (const linked of this.linkedTo.keys()) {
|
|
38558
|
+
if (linked !== district) this.managers.get(linked).invalidate();
|
|
38559
|
+
}
|
|
38560
|
+
}
|
|
38561
|
+
return true;
|
|
38562
|
+
}
|
|
38360
38563
|
async ensureDiscovery() {
|
|
38361
|
-
await this.
|
|
38564
|
+
const session = await this.managers.get(this.primaryName).ensure();
|
|
38362
38565
|
if (this.fetchproxyMode && !this.fetchproxyDiscoveryRan) {
|
|
38363
38566
|
this.fetchproxyDiscoveryRan = true;
|
|
38364
|
-
await this.discoverLinkedDistricts(this.accounts.get(this.primaryName));
|
|
38567
|
+
await this.discoverLinkedDistricts(this.accounts.get(this.primaryName), session);
|
|
38365
38568
|
}
|
|
38366
38569
|
}
|
|
38367
38570
|
listDistricts() {
|
|
@@ -38394,50 +38597,37 @@ var ICClient = class {
|
|
|
38394
38597
|
account2 = this.accounts.get(district);
|
|
38395
38598
|
if (!account2) throw new UnknownDistrictError(district, [...this.accounts.keys()]);
|
|
38396
38599
|
}
|
|
38397
|
-
await this.
|
|
38600
|
+
const session = await this.managers.get(account2.name).ensure();
|
|
38398
38601
|
if (this.fetchproxyMode && !this.fetchproxyDiscoveryRan && account2.name === this.primaryName) {
|
|
38399
38602
|
this.fetchproxyDiscoveryRan = true;
|
|
38400
|
-
await this.discoverLinkedDistricts(account2);
|
|
38401
|
-
}
|
|
38402
|
-
return this.doRequest(account2, path, opts, false);
|
|
38403
|
-
}
|
|
38404
|
-
async ensureSession(account2) {
|
|
38405
|
-
let s = this.sessions.get(account2.name);
|
|
38406
|
-
if (s && Date.now() - s.loggedInAt < SESSION_TTL_MS) return;
|
|
38407
|
-
if (s?.loginInFlight) {
|
|
38408
|
-
await s.loginInFlight;
|
|
38409
|
-
return;
|
|
38410
|
-
}
|
|
38411
|
-
if (!s) {
|
|
38412
|
-
s = { cookie: "", xsrfToken: "", loggedInAt: 0, loginInFlight: null };
|
|
38413
|
-
this.sessions.set(account2.name, s);
|
|
38414
|
-
}
|
|
38415
|
-
s.loginInFlight = this.login(account2);
|
|
38416
|
-
try {
|
|
38417
|
-
await s.loginInFlight;
|
|
38418
|
-
} finally {
|
|
38419
|
-
s.loginInFlight = null;
|
|
38603
|
+
await this.discoverLinkedDistricts(account2, session);
|
|
38420
38604
|
}
|
|
38605
|
+
return this.doRequest(account2, path, opts);
|
|
38421
38606
|
}
|
|
38422
38607
|
async login(account2) {
|
|
38423
38608
|
const primaryName = this.linkedTo.get(account2.name);
|
|
38424
38609
|
if (primaryName) {
|
|
38425
|
-
this.
|
|
38426
|
-
const
|
|
38427
|
-
|
|
38428
|
-
if (!this.sessions.has(account2.name)) {
|
|
38610
|
+
await this.managers.get(primaryName).ensure();
|
|
38611
|
+
const restored = this.managers.get(account2.name).current;
|
|
38612
|
+
if (!restored) {
|
|
38429
38613
|
throw new AuthFailedError(
|
|
38430
38614
|
account2.name,
|
|
38431
38615
|
"linked-district session could not be re-established via the primary district",
|
|
38432
38616
|
{ credentialHint: false }
|
|
38433
38617
|
);
|
|
38434
38618
|
}
|
|
38435
|
-
return;
|
|
38619
|
+
return restored;
|
|
38620
|
+
}
|
|
38621
|
+
if (this.preloaded) {
|
|
38622
|
+
const { cookieHeader, xsrfToken } = this.preloaded;
|
|
38623
|
+
this.preloaded = null;
|
|
38624
|
+
return { cookieHeader, xsrfToken };
|
|
38436
38625
|
}
|
|
38437
38626
|
if (!account2.username || !account2.password) {
|
|
38438
38627
|
throw new AuthFailedError(
|
|
38439
38628
|
account2.name,
|
|
38440
|
-
"session expired and no IC_USERNAME/IC_PASSWORD set \u2014 sign back into your IC portal in the browser and restart the MCP"
|
|
38629
|
+
"session expired and no IC_USERNAME/IC_PASSWORD set \u2014 sign back into your IC portal in the browser and restart the MCP",
|
|
38630
|
+
{ permanent: true }
|
|
38441
38631
|
);
|
|
38442
38632
|
}
|
|
38443
38633
|
const postRes = await fetch(
|
|
@@ -38470,17 +38660,17 @@ var ICClient = class {
|
|
|
38470
38660
|
if (!cookies.cookieHeader) {
|
|
38471
38661
|
throw new AuthFailedError(account2.name, "login response missing session cookies");
|
|
38472
38662
|
}
|
|
38473
|
-
const session =
|
|
38474
|
-
|
|
38475
|
-
|
|
38476
|
-
|
|
38477
|
-
await this.discoverLinkedDistricts(account2);
|
|
38663
|
+
const session = {
|
|
38664
|
+
cookieHeader: cookies.cookieHeader,
|
|
38665
|
+
xsrfToken: cookies.xsrfToken
|
|
38666
|
+
};
|
|
38667
|
+
await this.discoverLinkedDistricts(account2, session);
|
|
38668
|
+
return session;
|
|
38478
38669
|
}
|
|
38479
|
-
async discoverLinkedDistricts(account2) {
|
|
38670
|
+
async discoverLinkedDistricts(account2, session) {
|
|
38480
38671
|
try {
|
|
38481
|
-
const session = this.sessions.get(account2.name);
|
|
38482
38672
|
const baseHeaders = {
|
|
38483
|
-
Cookie: session.
|
|
38673
|
+
Cookie: session.cookieHeader,
|
|
38484
38674
|
Accept: "application/json",
|
|
38485
38675
|
"Content-Type": "application/json",
|
|
38486
38676
|
Referer: `${account2.baseUrl}/campus/nav-wrapper/`,
|
|
@@ -38548,13 +38738,14 @@ var ICClient = class {
|
|
|
38548
38738
|
password: "(linked)"
|
|
38549
38739
|
};
|
|
38550
38740
|
this.accounts.set(linked.districtName, syntheticAccount);
|
|
38551
|
-
this.sessions.set(linked.districtName, {
|
|
38552
|
-
cookie: switchCookies.cookieHeader,
|
|
38553
|
-
xsrfToken: switchCookies.xsrfToken,
|
|
38554
|
-
loggedInAt: Date.now(),
|
|
38555
|
-
loginInFlight: null
|
|
38556
|
-
});
|
|
38557
38741
|
this.linkedTo.set(linked.districtName, account2.name);
|
|
38742
|
+
if (!this.managers.has(linked.districtName)) {
|
|
38743
|
+
this.managers.set(linked.districtName, this.makeManager(syntheticAccount));
|
|
38744
|
+
}
|
|
38745
|
+
this.managers.get(linked.districtName).seed({
|
|
38746
|
+
cookieHeader: switchCookies.cookieHeader,
|
|
38747
|
+
xsrfToken: switchCookies.xsrfToken
|
|
38748
|
+
});
|
|
38558
38749
|
console.error(`[ic] Linked district discovered: ${linked.districtName} (${linked.appName})`);
|
|
38559
38750
|
} catch (e) {
|
|
38560
38751
|
console.error(`[ic] CUPS flow failed for ${linked.districtName}: ${e instanceof Error ? e.message : e}`);
|
|
@@ -38580,12 +38771,11 @@ var ICClient = class {
|
|
|
38580
38771
|
}
|
|
38581
38772
|
const account2 = this.accounts.get(district);
|
|
38582
38773
|
if (!account2) throw new UnknownDistrictError(district, [...this.accounts.keys()]);
|
|
38583
|
-
await this.
|
|
38584
|
-
const session = this.sessions.get(account2.name);
|
|
38774
|
+
const session = await this.managers.get(account2.name).ensure();
|
|
38585
38775
|
const url2 = /^https?:\/\//i.test(path) ? path : `${account2.baseUrl}${path}`;
|
|
38586
38776
|
const res = await fetch(url2, {
|
|
38587
38777
|
headers: {
|
|
38588
|
-
Cookie: session.
|
|
38778
|
+
Cookie: session.cookieHeader,
|
|
38589
38779
|
...session.xsrfToken ? { "X-XSRF-TOKEN": session.xsrfToken } : {}
|
|
38590
38780
|
}
|
|
38591
38781
|
});
|
|
@@ -38598,34 +38788,23 @@ var ICClient = class {
|
|
|
38598
38788
|
contentType: res.headers.get("content-type") ?? "application/octet-stream"
|
|
38599
38789
|
};
|
|
38600
38790
|
}
|
|
38601
|
-
async doRequest(account2, path, opts
|
|
38602
|
-
const
|
|
38791
|
+
async doRequest(account2, path, opts) {
|
|
38792
|
+
const mgr = this.managers.get(account2.name);
|
|
38603
38793
|
const accept = opts.responseType === "text" ? "text/html, text/plain, */*" : "application/json";
|
|
38604
|
-
const res = await
|
|
38605
|
-
|
|
38606
|
-
|
|
38607
|
-
|
|
38608
|
-
|
|
38609
|
-
|
|
38610
|
-
|
|
38611
|
-
|
|
38612
|
-
|
|
38613
|
-
|
|
38794
|
+
const res = await mgr.withSession(
|
|
38795
|
+
async (session) => fetch(`${account2.baseUrl}${path}`, {
|
|
38796
|
+
method: opts.method ?? "GET",
|
|
38797
|
+
headers: {
|
|
38798
|
+
Cookie: session.cookieHeader,
|
|
38799
|
+
Accept: accept,
|
|
38800
|
+
...session.xsrfToken ? { "X-XSRF-TOKEN": session.xsrfToken } : {},
|
|
38801
|
+
...opts.headers ?? {}
|
|
38802
|
+
},
|
|
38803
|
+
body: opts.body
|
|
38804
|
+
})
|
|
38805
|
+
);
|
|
38614
38806
|
if (res.status === 401) {
|
|
38615
|
-
|
|
38616
|
-
this.sessions.delete(account2.name);
|
|
38617
|
-
const primaryName = this.linkedTo.get(account2.name);
|
|
38618
|
-
if (primaryName) {
|
|
38619
|
-
this.sessions.delete(primaryName);
|
|
38620
|
-
for (const [linked] of this.linkedTo) {
|
|
38621
|
-
this.sessions.delete(linked);
|
|
38622
|
-
}
|
|
38623
|
-
const primaryAccount = this.accounts.get(primaryName);
|
|
38624
|
-
await this.ensureSession(primaryAccount);
|
|
38625
|
-
} else {
|
|
38626
|
-
await this.ensureSession(account2);
|
|
38627
|
-
}
|
|
38628
|
-
return this.doRequest(account2, path, opts, true);
|
|
38807
|
+
throw new SessionExpiredError(account2.name);
|
|
38629
38808
|
}
|
|
38630
38809
|
if (res.status >= 500) throw new PortalUnreachableError(account2.name, res.status);
|
|
38631
38810
|
if (!res.ok) throw new Error(`IC ${res.status} ${res.statusText} for ${path}`);
|
|
@@ -38638,14 +38817,11 @@ var ICClient = class {
|
|
|
38638
38817
|
};
|
|
38639
38818
|
function parseSetCookies(headers) {
|
|
38640
38819
|
const raw = headers.getSetCookie?.() ?? [];
|
|
38641
|
-
const
|
|
38642
|
-
|
|
38820
|
+
const { cookies, cookieHeader } = parseCookieJar(
|
|
38821
|
+
raw.length > 0 ? raw : headers.get("set-cookie")
|
|
38822
|
+
);
|
|
38643
38823
|
return { cookieHeader, xsrfToken: cookies["XSRF-TOKEN"] ?? "" };
|
|
38644
38824
|
}
|
|
38645
|
-
function splitFallback(header) {
|
|
38646
|
-
if (!header) return [];
|
|
38647
|
-
return header.split(",").map((s) => s.trim());
|
|
38648
|
-
}
|
|
38649
38825
|
var UnknownDistrictError = class extends Error {
|
|
38650
38826
|
constructor(district, available) {
|
|
38651
38827
|
super(`Unknown district '${district}'. Configured: [${available.join(", ")}]`);
|
|
@@ -38662,6 +38838,7 @@ var AuthFailedError = class extends Error {
|
|
|
38662
38838
|
* "Check IC_USERNAME and IC_PASSWORD" suffix — used for failures where the
|
|
38663
38839
|
* credentials are known-good (e.g. a linked-district CUPS/SSO re-discovery
|
|
38664
38840
|
* failure) and pointing the user at their creds would be misleading.
|
|
38841
|
+
* @param opts.permanent See {@link AuthFailedError.permanent}.
|
|
38665
38842
|
*/
|
|
38666
38843
|
constructor(district, reason, opts) {
|
|
38667
38844
|
const detail = reason ? ` (${reason})` : "";
|
|
@@ -38670,9 +38847,17 @@ var AuthFailedError = class extends Error {
|
|
|
38670
38847
|
this.district = district;
|
|
38671
38848
|
this.reason = reason;
|
|
38672
38849
|
this.name = "AuthFailedError";
|
|
38850
|
+
this.permanent = opts?.permanent ?? false;
|
|
38673
38851
|
}
|
|
38674
38852
|
district;
|
|
38675
38853
|
reason;
|
|
38854
|
+
/**
|
|
38855
|
+
* A *permanent* configuration failure: retrying the login inside this
|
|
38856
|
+
* process can never succeed (e.g. fetchproxy mode with no creds — the user
|
|
38857
|
+
* must re-sign-in in the browser and restart). CookieSessionManager caches
|
|
38858
|
+
* and rethrows permanent errors instead of retrying.
|
|
38859
|
+
*/
|
|
38860
|
+
permanent;
|
|
38676
38861
|
};
|
|
38677
38862
|
var PortalUnreachableError = class extends Error {
|
|
38678
38863
|
constructor(district, status) {
|
|
@@ -38717,40 +38902,6 @@ var FileExistsError = class extends Error {
|
|
|
38717
38902
|
path;
|
|
38718
38903
|
};
|
|
38719
38904
|
|
|
38720
|
-
// src/tools/_shared.ts
|
|
38721
|
-
function textContent(data) {
|
|
38722
|
-
return textResult(data);
|
|
38723
|
-
}
|
|
38724
|
-
function is404(e) {
|
|
38725
|
-
return e instanceof Error && e.message.startsWith("IC 404 ");
|
|
38726
|
-
}
|
|
38727
|
-
function featureDisabled(feature, district, data = []) {
|
|
38728
|
-
return textContent({ warning: "FeatureDisabled", feature, district, data });
|
|
38729
|
-
}
|
|
38730
|
-
async function findStudent(client, district, studentId) {
|
|
38731
|
-
const students = await client.request(district, "/campus/api/portal/students");
|
|
38732
|
-
return students.find((s) => String(s.personID) === studentId) ?? null;
|
|
38733
|
-
}
|
|
38734
|
-
function studentNotFound(studentId) {
|
|
38735
|
-
return textContent({ error: "StudentNotFound", studentId });
|
|
38736
|
-
}
|
|
38737
|
-
async function checkFeatureDisabled(client, district, studentId, student, flag, toolName, emptyData = []) {
|
|
38738
|
-
const structureID = student.enrollments?.[0]?.structureID;
|
|
38739
|
-
if (!structureID) return null;
|
|
38740
|
-
try {
|
|
38741
|
-
const features = await client.getFeatures(district, structureID, studentId);
|
|
38742
|
-
if (features[flag] === false) return featureDisabled(toolName, district, emptyData);
|
|
38743
|
-
return null;
|
|
38744
|
-
} catch (e) {
|
|
38745
|
-
console.error(`[ic] displayOptions check failed for ${district}/${flag}: ${e instanceof Error ? e.message : e}`);
|
|
38746
|
-
return null;
|
|
38747
|
-
}
|
|
38748
|
-
}
|
|
38749
|
-
function toArray(value) {
|
|
38750
|
-
if (value === null || value === void 0) return [];
|
|
38751
|
-
return Array.isArray(value) ? value : [value];
|
|
38752
|
-
}
|
|
38753
|
-
|
|
38754
38905
|
// src/tools/districts.ts
|
|
38755
38906
|
function registerDistrictTools(server, client) {
|
|
38756
38907
|
server.registerTool("ic_list_districts", {
|
|
@@ -38759,7 +38910,7 @@ function registerDistrictTools(server, client) {
|
|
|
38759
38910
|
}, async () => {
|
|
38760
38911
|
await client.ensureDiscovery();
|
|
38761
38912
|
const data = client.listDistricts();
|
|
38762
|
-
return
|
|
38913
|
+
return textResult(data);
|
|
38763
38914
|
});
|
|
38764
38915
|
}
|
|
38765
38916
|
|
|
@@ -38775,7 +38926,7 @@ function registerStudentTools(server, client) {
|
|
|
38775
38926
|
}, async (rawArgs) => {
|
|
38776
38927
|
const args = argsSchema.parse(rawArgs);
|
|
38777
38928
|
const data = await client.request(args.district, "/campus/api/portal/students");
|
|
38778
|
-
return
|
|
38929
|
+
return textResult(data);
|
|
38779
38930
|
});
|
|
38780
38931
|
}
|
|
38781
38932
|
|
|
@@ -38795,7 +38946,7 @@ function registerScheduleTools(server, client) {
|
|
|
38795
38946
|
const args = argsSchema2.parse(rawArgs);
|
|
38796
38947
|
const params = new URLSearchParams({ personID: args.studentId });
|
|
38797
38948
|
const data = await client.request(args.district, `/campus/resources/portal/roster?${params}`);
|
|
38798
|
-
return
|
|
38949
|
+
return textResult(data);
|
|
38799
38950
|
});
|
|
38800
38951
|
}
|
|
38801
38952
|
|
|
@@ -38833,7 +38984,7 @@ function registerAssignmentTools(server, client) {
|
|
|
38833
38984
|
if (args.missingOnly) {
|
|
38834
38985
|
data = data.filter((a) => a.missing);
|
|
38835
38986
|
}
|
|
38836
|
-
return
|
|
38987
|
+
return textResult(data);
|
|
38837
38988
|
});
|
|
38838
38989
|
}
|
|
38839
38990
|
|
|
@@ -38853,10 +39004,41 @@ function registerGradeTools(server, client) {
|
|
|
38853
39004
|
const params = new URLSearchParams({ personID: args.studentId });
|
|
38854
39005
|
if (args.termId) params.set("termID", args.termId);
|
|
38855
39006
|
const data = await client.request(args.district, `/campus/resources/portal/grades?${params}`);
|
|
38856
|
-
return
|
|
39007
|
+
return textResult(data);
|
|
38857
39008
|
});
|
|
38858
39009
|
}
|
|
38859
39010
|
|
|
39011
|
+
// src/tools/_shared.ts
|
|
39012
|
+
function is404(e) {
|
|
39013
|
+
return e instanceof Error && e.message.startsWith("IC 404 ");
|
|
39014
|
+
}
|
|
39015
|
+
function featureDisabled(feature, district, data = []) {
|
|
39016
|
+
return textResult({ warning: "FeatureDisabled", feature, district, data });
|
|
39017
|
+
}
|
|
39018
|
+
async function findStudent(client, district, studentId) {
|
|
39019
|
+
const students = await client.request(district, "/campus/api/portal/students");
|
|
39020
|
+
return students.find((s) => String(s.personID) === studentId) ?? null;
|
|
39021
|
+
}
|
|
39022
|
+
function studentNotFound(studentId) {
|
|
39023
|
+
return textResult({ error: "StudentNotFound", studentId });
|
|
39024
|
+
}
|
|
39025
|
+
async function checkFeatureDisabled(client, district, studentId, student, flag, toolName, emptyData = []) {
|
|
39026
|
+
const structureID = student.enrollments?.[0]?.structureID;
|
|
39027
|
+
if (!structureID) return null;
|
|
39028
|
+
try {
|
|
39029
|
+
const features = await client.getFeatures(district, structureID, studentId);
|
|
39030
|
+
if (features[flag] === false) return featureDisabled(toolName, district, emptyData);
|
|
39031
|
+
return null;
|
|
39032
|
+
} catch (e) {
|
|
39033
|
+
console.error(`[ic] displayOptions check failed for ${district}/${flag}: ${e instanceof Error ? e.message : e}`);
|
|
39034
|
+
return null;
|
|
39035
|
+
}
|
|
39036
|
+
}
|
|
39037
|
+
function toArray(value) {
|
|
39038
|
+
if (value === null || value === void 0) return [];
|
|
39039
|
+
return Array.isArray(value) ? value : [value];
|
|
39040
|
+
}
|
|
39041
|
+
|
|
38860
39042
|
// src/tools/attendance.ts
|
|
38861
39043
|
var argsSchema5 = external_exports.object({
|
|
38862
39044
|
district: external_exports.string(),
|
|
@@ -38921,7 +39103,7 @@ function registerAttendanceTools(server, client) {
|
|
|
38921
39103
|
results.push({ ...entry, terms: trimmedTerms });
|
|
38922
39104
|
}
|
|
38923
39105
|
}
|
|
38924
|
-
return
|
|
39106
|
+
return textResult(results);
|
|
38925
39107
|
} catch (e) {
|
|
38926
39108
|
if (is404(e)) return featureDisabled("attendance", args.district);
|
|
38927
39109
|
throw e;
|
|
@@ -38952,7 +39134,7 @@ function registerBehaviorTools(server, client) {
|
|
|
38952
39134
|
if (args.until) params.set("endDate", args.until);
|
|
38953
39135
|
try {
|
|
38954
39136
|
const data = await client.request(args.district, `/campus/resources/portal/behavior?${params}`);
|
|
38955
|
-
return
|
|
39137
|
+
return textResult(data);
|
|
38956
39138
|
} catch (e) {
|
|
38957
39139
|
if (is404(e)) return featureDisabled("behavior", args.district);
|
|
38958
39140
|
throw e;
|
|
@@ -38992,7 +39174,7 @@ function registerFoodServiceTools(server, client) {
|
|
|
38992
39174
|
if (args.until) params.set("endDate", args.until);
|
|
38993
39175
|
try {
|
|
38994
39176
|
const data = await client.request(args.district, `/campus/resources/portal/foodService?${params}`);
|
|
38995
|
-
return
|
|
39177
|
+
return textResult(data);
|
|
38996
39178
|
} catch (e) {
|
|
38997
39179
|
if (is404(e)) return featureDisabled("foodService", args.district, EMPTY_FOOD);
|
|
38998
39180
|
throw e;
|
|
@@ -39012,16 +39194,50 @@ var NAMED_ENTITIES = {
|
|
|
39012
39194
|
function extractPlainTextFromHtml(html) {
|
|
39013
39195
|
if (!html)
|
|
39014
39196
|
return "";
|
|
39015
|
-
let text = html
|
|
39197
|
+
let text = stripElementContent(stripElementContent(html, "script"), "style");
|
|
39016
39198
|
text = text.replace(/<[^>]+>/g, " ");
|
|
39017
|
-
text = text.replace(/&#(\d+);/g, (
|
|
39018
|
-
text = text.replace(/&#x([0-9a-fA-F]+);/g, (
|
|
39199
|
+
text = text.replace(/&#(\d+);/g, (whole, d) => codePointOr(Number(d), whole));
|
|
39200
|
+
text = text.replace(/&#x([0-9a-fA-F]+);/g, (whole, h) => codePointOr(parseInt(h, 16), whole));
|
|
39019
39201
|
text = text.replace(/&([a-zA-Z]+);/g, (whole, name) => {
|
|
39020
39202
|
const decoded = NAMED_ENTITIES[name.toLowerCase()];
|
|
39021
39203
|
return decoded ?? whole;
|
|
39022
39204
|
});
|
|
39023
39205
|
return text.replace(/\s+/g, " ").trim();
|
|
39024
39206
|
}
|
|
39207
|
+
function stripElementContent(html, tag) {
|
|
39208
|
+
const lower = html.toLowerCase();
|
|
39209
|
+
const open = `<${tag}`;
|
|
39210
|
+
const close = `</${tag}>`;
|
|
39211
|
+
let out = "";
|
|
39212
|
+
let i = 0;
|
|
39213
|
+
for (; ; ) {
|
|
39214
|
+
const start = lower.indexOf(open, i);
|
|
39215
|
+
if (start < 0) {
|
|
39216
|
+
out += html.slice(i);
|
|
39217
|
+
return out;
|
|
39218
|
+
}
|
|
39219
|
+
const after = lower[start + open.length];
|
|
39220
|
+
if (after !== void 0 && after !== ">" && after !== " " && after !== " " && after !== "\n" && after !== "\r" && after !== "\f" && after !== "/") {
|
|
39221
|
+
out += html.slice(i, start + open.length);
|
|
39222
|
+
i = start + open.length;
|
|
39223
|
+
continue;
|
|
39224
|
+
}
|
|
39225
|
+
out += html.slice(i, start) + " ";
|
|
39226
|
+
const closeAt = lower.indexOf(close, start + open.length);
|
|
39227
|
+
if (closeAt < 0)
|
|
39228
|
+
return out;
|
|
39229
|
+
i = closeAt + close.length;
|
|
39230
|
+
}
|
|
39231
|
+
}
|
|
39232
|
+
function codePointOr(code, raw) {
|
|
39233
|
+
if (!Number.isInteger(code) || code < 0 || code > 1114111)
|
|
39234
|
+
return raw;
|
|
39235
|
+
try {
|
|
39236
|
+
return String.fromCodePoint(code);
|
|
39237
|
+
} catch {
|
|
39238
|
+
return raw;
|
|
39239
|
+
}
|
|
39240
|
+
}
|
|
39025
39241
|
|
|
39026
39242
|
// src/tools/messages.ts
|
|
39027
39243
|
var listArgs = external_exports.object({
|
|
@@ -39116,7 +39332,7 @@ function registerMessageTools(server, client) {
|
|
|
39116
39332
|
return { count: 0, items: [], error: e instanceof Error ? e.message : String(e) };
|
|
39117
39333
|
});
|
|
39118
39334
|
const [notifications, inbox, announcements] = await Promise.all([prismPromise, inboxPromise, noticePromise]);
|
|
39119
|
-
return
|
|
39335
|
+
return textResult({ notifications, inbox, announcements });
|
|
39120
39336
|
});
|
|
39121
39337
|
server.registerTool("ic_get_message", {
|
|
39122
39338
|
description: "Fetch the HTML body of an inbox message and return it parsed into { subject, date, body, url }. Takes a `messageUrl` which is the `url` field from an item returned by ic_list_messages' inbox section (e.g. 'portal/messageView.xsl?x=messenger.MessengerEngine-getMessageRecipientView&messageID=...'). Relative and /campus/-prefixed URLs are both accepted. Note: fetching the HTML body may mark the message as read on some district configurations; probe against an empty inbox could not confirm the side effect.",
|
|
@@ -39127,7 +39343,7 @@ function registerMessageTools(server, client) {
|
|
|
39127
39343
|
const path = normalizeMessageUrl(args.messageUrl);
|
|
39128
39344
|
const html = await client.request(args.district, path, { responseType: "text" });
|
|
39129
39345
|
const parsed = parseMessageHtml(html ?? "", path);
|
|
39130
|
-
return
|
|
39346
|
+
return textResult(parsed);
|
|
39131
39347
|
});
|
|
39132
39348
|
}
|
|
39133
39349
|
|
|
@@ -39167,7 +39383,7 @@ function registerDocumentTools(server, client) {
|
|
|
39167
39383
|
if (d.endYear !== void 0) out.endYear = d.endYear;
|
|
39168
39384
|
return out;
|
|
39169
39385
|
});
|
|
39170
|
-
return
|
|
39386
|
+
return textResult(trimmed);
|
|
39171
39387
|
} catch (e) {
|
|
39172
39388
|
if (is404(e)) return featureDisabled("documents", args.district);
|
|
39173
39389
|
throw e;
|
|
@@ -39183,10 +39399,10 @@ function registerDocumentTools(server, client) {
|
|
|
39183
39399
|
const meta3 = await client.download(args.district, args.documentId, args.destinationPath, {
|
|
39184
39400
|
overwrite: args.overwrite ?? false
|
|
39185
39401
|
});
|
|
39186
|
-
return
|
|
39402
|
+
return textResult(meta3);
|
|
39187
39403
|
} catch (e) {
|
|
39188
39404
|
if (e instanceof Error && e.message.startsWith("IC download 404")) {
|
|
39189
|
-
return
|
|
39405
|
+
return textResult({ warning: "FeatureDisabled", feature: "documents", district: args.district });
|
|
39190
39406
|
}
|
|
39191
39407
|
throw e;
|
|
39192
39408
|
}
|
|
@@ -39242,7 +39458,7 @@ function registerCalendarTools(server, client) {
|
|
|
39242
39458
|
terms: trimmedTerms
|
|
39243
39459
|
});
|
|
39244
39460
|
}
|
|
39245
|
-
return
|
|
39461
|
+
return textResult(result);
|
|
39246
39462
|
});
|
|
39247
39463
|
}
|
|
39248
39464
|
|
|
@@ -39333,7 +39549,7 @@ function registerAttendanceEventsTools(server, client) {
|
|
|
39333
39549
|
results.push(trimmed);
|
|
39334
39550
|
}
|
|
39335
39551
|
}
|
|
39336
|
-
return
|
|
39552
|
+
return textResult(results);
|
|
39337
39553
|
} catch (e) {
|
|
39338
39554
|
if (is404(e)) return featureDisabled("attendance_events", args.district);
|
|
39339
39555
|
throw e;
|
|
@@ -39389,7 +39605,7 @@ function registerRecentGradesTools(server, client) {
|
|
|
39389
39605
|
}
|
|
39390
39606
|
return out;
|
|
39391
39607
|
});
|
|
39392
|
-
return
|
|
39608
|
+
return textResult(trimmed);
|
|
39393
39609
|
});
|
|
39394
39610
|
}
|
|
39395
39611
|
|
|
@@ -39444,7 +39660,7 @@ function registerTeacherTools(server, client) {
|
|
|
39444
39660
|
const [teachersRaw, counselorsRaw] = await Promise.all([teachersPromise, counselorsPromise]);
|
|
39445
39661
|
const teachers = toArray(teachersRaw).map((t) => trimRecord(t));
|
|
39446
39662
|
const counselors = toArray(counselorsRaw).map((c) => trimRecord(c));
|
|
39447
|
-
return
|
|
39663
|
+
return textResult({ counselors, teachers });
|
|
39448
39664
|
});
|
|
39449
39665
|
}
|
|
39450
39666
|
|
|
@@ -39495,7 +39711,7 @@ function registerAssessmentTools(server, client) {
|
|
|
39495
39711
|
if (feature404 && result.length === 0) {
|
|
39496
39712
|
return featureDisabled("assessments", args.district);
|
|
39497
39713
|
}
|
|
39498
|
-
return
|
|
39714
|
+
return textResult(result);
|
|
39499
39715
|
});
|
|
39500
39716
|
}
|
|
39501
39717
|
|
|
@@ -39539,7 +39755,7 @@ function registerFeeTools(server, client) {
|
|
|
39539
39755
|
feeAssignments: assignments.ok ? assignments.value : [],
|
|
39540
39756
|
...issues.length > 0 ? { issues } : {}
|
|
39541
39757
|
};
|
|
39542
|
-
return
|
|
39758
|
+
return textResult(response);
|
|
39543
39759
|
});
|
|
39544
39760
|
}
|
|
39545
39761
|
|
|
@@ -39567,7 +39783,7 @@ function registerFeaturesTools(server, client) {
|
|
|
39567
39783
|
features
|
|
39568
39784
|
});
|
|
39569
39785
|
}
|
|
39570
|
-
return
|
|
39786
|
+
return textResult(result);
|
|
39571
39787
|
});
|
|
39572
39788
|
}
|
|
39573
39789
|
|
|
@@ -39591,7 +39807,7 @@ try {
|
|
|
39591
39807
|
}
|
|
39592
39808
|
var COMMON = {
|
|
39593
39809
|
name: "infinitecampus",
|
|
39594
|
-
version: "2.3.
|
|
39810
|
+
version: "2.3.6"
|
|
39595
39811
|
// x-release-please-version
|
|
39596
39812
|
};
|
|
39597
39813
|
if (account) {
|