TypeScript monorepo that (1) wraps every direct operation of the
CUHK-Shenzhen Student Information System (Oracle PeopleSoft Campus Solutions,
https://sis.cuhk.edu.cn), and (2) solves course timetables on top of it.
packages/cuhksz-sislib all direct SIS operations (login, class search,
schedule, history, transcript, enrollment surface)
packages/sisarrange pure scheduling solver (abstract catalog +
constraints → ranked timetables; no SIS knowledge)
apps/web SvelteKit frontend (setup → pool → solve → sync);
its server side owns cuhksz-sislib (/api endpoints,
load functions, form actions)
Every mechanism in docs/sis-mechanism.md was verified against the live
system; the TS implementation is verified by the ported fixture tests
(pnpm test) and a read-only live pass (node scripts/verify_live.mjs <passphrase>). Design docs live in docs/design/.
pnpm install
pnpm build
pnpm typecheck
pnpm test
# log in once (interactive ADFS browser; requires `pnpm exec playwright install chromium`)
node -e "import('./packages/cuhksz-sislib/dist/index.js').then(async (m) => {
const c = new m.SisClient(m.defaultConfig({ headless: false }), process.argv[1]);
await c.login('user', 'password');
})" passphrase
# verify the saved session read-only against the live SIS
node scripts/verify_live.mjs <passphrase>
# run the web app (single process: pages + /api on http://localhost:5173)
pnpm devThe Python-era session (if you have one) unlocks with the same passphrase — the encrypted session format is shared.
const client = new SisClient(defaultConfig({ allowMutations: false }), passphrase);
// reads
await client.listTerms(); // Term[]
await client.listSubjects(); // [code, label][]
await client.searchClasses({ term, subject, openOnly, selectable, … });
await client.getWeeklySchedule();
await client.getCourseHistory();
await client.getAppointments(term?); // windows + term unit limits
await client.getExamSchedule(term?);
await client.getGrades(term?);
await client.getCart(term?); // shopping cart contents
await client.listTranscriptTypes();
await client.getTranscript("UE01"); // PDF bytes
await client.getMyAcademics();
await client.getMyApplication();
// mutations — throw MutationsDisabledError unless allowMutations: true
await client.cartAddByClassNbr(term, "3317");
await client.cartValidate(term);
await client.cartEnroll(term); // real enrollment
await client.enrollmentSubmit("drop"); // gated on allowUnverifiedSolver:
import { solve, normalizeConstraints } from "@cuhksz/sisarrange";
const constraints = normalizeConstraints({ required: [...], pick: [...],
maxUnits: 18, preferences: { freeDayBonus: 10 } });
const schedules = solve(catalog, constraints, { limit: 10 });docs/ mechanism notes, design docs, worklog
packages/cuhksz-sislib/ src/{api,features,peoplesoft,transport,models,protocol}
packages/sisarrange/ src/{model,constraints,solver,objective,validate,analyze,time,viability}
apps/web/ SvelteKit + Tailwind v4; src/routes/{api,…} server side,
src/lib state/adapter, solve Web Worker
scripts/ live verification tools (read-only)
See AGENTS.md for the contributor guide and security rules.