The CLI is organized around one rule: service logic returns typed values and
never prints. Commands turn those values into a CommandResult; the output
layer renders text, JSON, or JSONL.
command parser
↓
command handler validates intent and confirmation
↓
service client authentication, HTTP, pagination, normalization
↓
typed domain result
↓
output renderer text | versioned JSON | streaming JSONL
src/cli.tsowns command routing, option validation, and user-facing help.src/coreowns credentials, errors, output contracts, capabilities, consequences, and other shared primitives.src/core/keyring.tskeeps long-lived secrets behind platform adapters: macOS Keychain, Windows Credential Manager, Linux Secret Service, or (when Secret Service is unavailable) an encrypted local file store. CAS passwords and Blackboard native calendar links use separate secret namespaces. The local config contains profile metadata only for CAS credentials; the Blackboard calendar link does not create on-disk metadata.src/ssoowns generic CAS session flow. TIS, Blackboard, and WS reuse this layer instead of reimplementing login logic separately.src/tisowns TIS protocol details, normalized course models, persistent planning, TIS-reported degree-progress normalization, conservative derived missing-course classification, local degree audit, live classroom/context helpers, multi-source ICS export, read-only plan explain/recommend enrichment, and guarded enroll/cart/drop/bid write paths.src/calendar,src/faculty,src/online,src/transit,src/resources, andsrc/wifiown public or local-only data sources.src/onlineuses exact path allowlists and retains community provenance, freshness, and license metadata instead of presenting the source as official.src/contextcomposes a truthful snapshot from whichever sources are available, exposes Context v2level/liveenrichments, and marks missing or partial inputs explicitly.src/profileaggregates only whitelisted student fields and can save a versioned, private local report without exposing raw upstream profiles.src/academicand shared local-store helpers provide guarded, digest-verifiable local persistence, offline diffing, and the one-shot watch/update workflow.src/servicesowns reusable campus-service adapters such as Blackboard, WS, booking, library booking, PMS, NCES, and papers, plus the authenticated session wrappers that sit in front of some of them.src/services/library.tsowns direct Primo search/detail normalization, andsrc/services/library-browser.tsowns the browser-backed manual-auth Primo transport.src/services/blackboard-calendar.tsowns native Learn ICS-link validation, masking, safe same-origin fetch, and bounded ICS parsing for the stored Blackboard calendar subscription workflow.src/core/capabilities.tsis the machine-discoverable safety registry.src/mcpexposes a localstdioMCP adapter over that registry. It uses a typed tool allowlist plus JSON resources and prompts, launches the packaged CLI without a shell, propagates MCP cancellation to the child CLI process, and rejects authenticated personal data, remote mutations, browser-assisted flows, local private state, and known local state writes.- Services must not write to stdout or stderr.
- Machine-readable output is versioned by
schemaVersion. - Text is the default; agents opt into
--jsonor--jsonlexplicitly.
The repo now has three service-facing patterns:
-
Direct clients
Modules such as TIS, transit, calendar, papers, and NCES can create or own their HTTP access directly because they are either public or use the repo's built-in authentication flow.
-
Adapter-first clients
Modules in
src/servicesaccept aServiceAdapterwith afetch(...)method. This keeps parsers and endpoint knowledge reusable across:- the CLI
- future Web UI packages
- tests with fixture-backed adapters
- browser-backed or cookie-injected transports
-
Authenticated wrappers
Booking, library-booking, PMS, and the CLI's CAS-backed Blackboard/WS entrypoints now have repo-owned login wrappers that:
- retrieve credentials just in time and keep cookies and transient tokens in memory only
- constrain service origins; booking, library-booking, and PMS additionally enforce documented read allowlists and typed write endpoint allowlists
- normalize the resulting responses through the same typed service layer
sustech services status reports the reusable service layer, not only whether
a command exists. That is why booking, library-booking, and PMS now show
implemented, while Blackboard and WS still show adapter_required at the
service-module level even though the CLI already provides CAS-backed read
commands for them.
- Read commands never mutate remote state.
- Local planning commands such as
tis enroll preview,tis selection preview,tis bid plan,tis plan explain,tis plan recommend,academic changes, and one-shotacademic watchdo not mutate remote campus state. bb submit previewauthenticates for live read-only preflight checks but never calls a mutation endpoint.bb discussions,bb discussion, andbb discussion-repliesprefer the official Learn REST discussion endpoints with explicitoffset/limitpaging and server-side title, gradable, author, status, and read-state filters; for Blackboard Original courses that reject REST, forum, thread, and thread-detail reply reads fall back to the HTML discussion board, while group reads and discussion writes remain REST-only and fail closed.bb calendaris an authenticated read with optional date, type, and course filters.bb calendar-link setvalidates a native Learn ICS feed and stores it as a separate operating-system secret;showmasks it by default, andfetchcan refresh the feed without a fresh CAS login.bb attachmentskeeps teacher-provided content files separate from student attempt files.bb downloadandbb attempt-downloadare local mutations with explicit destinations, same-origin URL checks, exclusive no-overwrite placement, and a portable filesystem fallback when hard links are unavailable.bb attempt-filesexposes the student's submitted filenames without mixing them into the teacher-attachment surface.- Booking, library-booking, and PMS sessions keep credentials and session material in memory only, reject requests outside their allowlists, and never expose a generic authenticated write primitive.
library searchandlibrary detailsupport a direct public HTTP path plus--browserfallback. If the browser path reaches CAS, the user must finish that step manually. The CLI does not accept browser credentials, does not solve CAPTCHAs, and does not persist browser cookies.auth loginverifies the selected service before storing a password in the operating-system credential store. When Linux Secret Service is unavailable, the CLI falls back to an encrypted local file store (AES-256-GCM) that requires a master password. It never falls back to plaintext storage or session-only kernel keyrings.- If CAS responds with an interactive slide CAPTCHA, the shared login layer
returns
CAS_INTERACTIVE_CHALLENGE_REQUIREDbefore password submission instead of trying to bypass the challenge. - Remote mutations cover TIS enroll/cart/drop/bid, Blackboard submission,
eHall and library booking create/cancel, and PMS queue upload/delete. Every
path requires
--confirm; file-bound uploads additionally require the previewed SHA-256. These paths have protocol-fixture coverage. An authorized individual Blackboard Original file resubmission additionally passed live submission and REST read-back on 2026-09-11. - Blackboard Original submission reads a fresh assignment form with CAS cookies and sends one multipart HTTP POST. Nonces stay in memory; redirects cannot replay the POST. REST read-back must identify one new submitted attempt and verify its file/text and optional comment before reporting success.
- eHall booking and library-booking create previews attempt exact slot availability checks first and fail closed when the live evidence is missing, malformed, or ambiguous.
- Local file mutations such as Blackboard download/sync, OA PDF fetch,
iCalendar/profile/snapshot export, and plan persistence require explicit or
well-scoped destinations, reject symbolic-link traversal, default to
no-overwrite, and request mode
0600for personal academic artifacts on POSIX platforms. Windows uses the destination filesystem's ACLs rather than POSIX mode bits. - Mutation commands use explicit preview/build phases and post-action
verification. Any ambiguous remote result returns exit code 5 plus
DO_NOT_RETRY_AUTOMATICALLYwhen write state cannot be determined safely. - TIS selection previews carry a local correlation ID but never claim upstream
idempotency. Transport ambiguity is reconciled through bounded exact
{courseId, rwh, round}reads rather than by repeating a mutation. - Planning-facing TIS output passes through field-allowlisted projections; broad upstream rows and raw mutation responses remain outside CLI JSON.
- Consequence metadata lives in
src/core/consequences.tsso agents can inspect risks and follow-up checks without scraping prose. - New authenticated campus-service wrappers are validated with protocol fixtures and transport guards. Opt-in read-only live smoke tests passed for booking and library-booking on 2026-08-26; PMS remained blocked by its campus-network gate.
As the rewrite grows, command routing will likely split out of src/cli.ts,
but the core rule stays the same: domain modules return typed values, renderers
format them, and the CLI layer is where auth, confirmation, and output mode
selection come together.