From d22d492fa760a0f2d09b90a3bea9f230ed01cd41 Mon Sep 17 00:00:00 2001 From: Grada Date: Fri, 18 Sep 2026 16:29:43 +0800 Subject: [PATCH] docs: refresh README presentation --- README.md | 635 +++++++++---------------------- docs/assets/sustech-cli-hero.png | Bin 0 -> 49952 bytes docs/assets/sustech-cli.svg | 15 + 3 files changed, 205 insertions(+), 445 deletions(-) create mode 100644 docs/assets/sustech-cli-hero.png create mode 100644 docs/assets/sustech-cli.svg diff --git a/README.md b/README.md index 4e411ab..fd0cc0e 100644 --- a/README.md +++ b/README.md @@ -1,526 +1,266 @@ -# sustech-cli - -[![npm](https://img.shields.io/npm/v/sustech-cli)](https://www.npmjs.com/package/sustech-cli) -[![CI](https://github.com/wormforce/sustech-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/wormforce/sustech-cli/actions/workflows/ci.yml) -[![Node.js](https://img.shields.io/node/v/sustech-cli)](https://www.npmjs.com/package/sustech-cli) - -An unofficial TypeScript CLI for SUSTech services, designed for people, -scripts, and coding agents. Text is the default for humans; versioned JSON and -JSONL are available for software. Python is not required at runtime. - -> [!WARNING] +

+ sustech cli +

+ +

+ One calm command line for life at SUSTech.
+ Courses, Blackboard, calendar, library, campus services, and agent-ready context. +

+ +

+ npm + CI + Node.js + license +

+ +

+ Quick start · + What it does · + AI assistants · + Safety · + Docs +

+ +

+ sustech-cli — a polished command line for SUSTech services +

+ +`sustech-cli` brings frequently used SUSTech services into one consistent +TypeScript CLI. It is pleasant in a terminal, predictable in scripts, and +self-describing for coding agents. Human-readable text is the default; +versioned JSON and JSONL are available whenever software needs a stable +interface. + +> [!IMPORTANT] > This is an independent community project, not an official SUSTech service. -> Published npm releases may lag `main`. Inspect `sustech version` and -> `sustech capabilities` on the installed copy before relying on a command or -> allowing it to change state. +> It never bypasses CAPTCHA or other interactive challenges. Review a command +> before allowing it to change university or local state. -## Install +## Quick Start -Requires Node.js 20.18 or newer: +Requires Node.js 20.18 or newer. ```bash npm install --global sustech-cli -sustech version +sustech ``` -To try one command without a global install: - -```bash -npm exec --package=sustech-cli -- sustech version -``` - -`npm run build` only compiles a source checkout; it does not put `sustech` on -your shell `PATH`. Package developers can use `npm install --global .` or -`npm link` after building. - -The CLI checks npm for a newer stable release at most once every 24 hours when -run in an interactive terminal. If one is available, it asks before updating. -JSON/JSONL output, redirected commands, and CI runs are never prompted. Use -`sustech update` to check immediately, `sustech update --yes` to install -without the confirmation prompt, or set `SUSTECH_DISABLE_UPDATE_CHECK=1` to -disable automatic checks. - -## Quick start - -Public data does not require an account: +The first screen shows the active account, runtime, and useful next actions. +Public commands work immediately: ```bash -sustech calendar day 2026-09-01 -sustech faculty search "computer vision" -sustech online search "校园卡" --section manual -sustech online manual list --source service --limit 10 -sustech online manual get ID_OR_TITLE -sustech online talks list --limit 10 +sustech calendar day sustech talks list -sustech online contact search "教学" -sustech nces filter-options -sustech nces browse --offering-unit "计算机科学与工程系" --page-size 5 -sustech nces global-stats -sustech nces rankings top-teachers --limit 5 -sustech nces by-code CS302 --term 20252 -sustech nces reviews 244 --sort newest --page-size 5 -sustech transit lines sustech library search "graph neural networks" --limit 5 +sustech faculty search "computer vision" ``` -Authenticated services use a named local profile: +Sign in once for personal services: ```bash sustech auth login -sustech auth status -sustech context --live --level verbose -sustech academic watch --state ./academic-state.json --include-blackboard -sustech academic changes before.json after.json -sustech tis plan recommend CS330 MA203 --round bxxk --max 5 -sustech tis plan explain CS330 --round bxxk -sustech tis degree missing -sustech tis degree progress -sustech bb calendar --type GradebookColumn -sustech bb announcements --days 14 -sustech bb deadlines --days 14 --submission-state not_attempted --json -sustech bb tree _8537_1 --max 50 -sustech bb types --course MSE306 -sustech bb roster _8537_1 --role Student --page-size 10 -sustech bb discussions _5325_1 --page-size 10 -sustech bb grades --course MSE306 --submission-state completed --limit 10 --json -sustech bb assignments --course MSE306 --with-attempts --json -sustech bb assignments _8537_1 --with-attempts --json -sustech bb assignments _8537_1 --submission-state not_attempted --json -sustech bb messages _8537_1 --folder-type Inbox --page-size 10 -sustech bb attempt-files _8537_1 _2201_1 +sustech context --live sustech tis schedule -sustech bb courses +sustech bb deadlines --days 14 ``` -Discover the complete command surface from the installed version: +Try a command without installing globally: + +```bash +npm exec --package=sustech-cli -- sustech version +``` + +## What It Does + +| Area | Useful commands | Access | +| --- | --- | --- | +| Daily snapshot | `context`, `profile show`, `academic changes` | Public calendar plus optional TIS and Blackboard reads | +| Teaching system | courses, schedule, grades, exams, degree progress, planning, iCalendar | SUSTech account | +| Blackboard | courses, content, assignments, deadlines, grades, announcements, discussions, files | SUSTech account | +| Campus calendar | teaching weeks, holidays, makeup days, term dates | Public | +| Library | live Primo search/detail, rooms and reservations | Public catalog; account for bookings | +| Campus services | classrooms, booking, printing, programs, Wi-Fi, transit | Public, local, or account-backed | +| Discovery | faculty, lectures, handbook, contacts, NCES, papers | Public | +| Agent interfaces | JSON, JSONL, Agent Skill, local MCP server | Local | + +The installed version is the source of truth: ```bash sustech --help sustech capabilities --json --pretty +sustech describe context --json --pretty ``` -## Use with an Agent +### A useful day in one command -The CLI is self-describing. Agents should inspect structured command and safety -metadata instead of parsing `--help` or relying on a memorized command list: +`context` creates a compact snapshot designed for people and assistants: +date, teaching week and parity, holiday or makeup-day rules, current and next +class, upcoming work, exams, weather, AQI, and library status. ```bash -sustech version --json -sustech capabilities --json -sustech consequences --json +sustech context --level terse +sustech context --live +sustech context --live --level verbose +sustech context --live --json ``` -This repository also ships a portable -[`sustech-cli` Agent Skill](skills/sustech-cli/SKILL.md). Install it directly -from the public repository—no source clone is needed: +Live sources run concurrently. Missing credentials and unavailable upstreams +are reported as partial data rather than silently turned into “nothing found.” +All academic times use Asia/Shanghai. + +### Live library catalog + +Library search reads the university's public Primo catalog directly, so results +stay current without shipping a large offline database: ```bash -npx skills add wormforce/sustech-cli --skill sustech-cli +sustech library search "三体" --limit 5 +sustech library detail L:alma991001055219704181 ``` -For a global Codex installation: +The normal path uses Primo's public JSON endpoints. A manual browser transport +is available when a host cannot complete the direct path: ```bash -npx skills add wormforce/sustech-cli --skill sustech-cli --global --agent codex +sustech library search "三体" --browser --interactive ``` -The [Agent Skills CLI](https://github.com/vercel-labs/skills) can target other -supported agents and project-local scopes. Review the Skill before installing -it: it teaches command discovery, structured output, credential boundaries, -preview/confirm workflows, and the rule never to retry an ambiguous mutation -automatically. - -Installing the npm package deliberately does **not** edit Codex, Claude Code, -Cursor, or other agent configuration. The target agent and scope are user -choices, so an npm `postinstall` script should not install instructions -silently. - -For an agent without Skill support, provide this short instruction: - -> Use the installed `sustech` CLI. Start with `sustech capabilities --json` and -> `sustech consequences --json`; request structured output, never expose login -> secrets, and never add `--confirm` without approval for the exact target. - -A Skill is the onboarding layer; the CLI remains the executable source of -truth. The package also ships a local `stdio` MCP entrypoint, `sustech-mcp`, for -clients that support native tools. It needs no hosted server and exposes `42` -typed read-only tools in total (`39` public allowlisted tools plus `3` metadata -tools), plus JSON resources, resource templates, and -prompts for discovery, public campus data, library, faculty, transit, NCES, -papers, and selected SUSTech Online reads. Authenticated data, browser flows, -local writes, and remote mutations remain unavailable through MCP. See -[docs/MCP.md](docs/MCP.md) for configuration and the complete boundary. -A repository-level `AGENTS.md` alone would only help agents that cloned the -source. - -## What it covers - -This table is a summary. Use `sustech capabilities --json` for the installed -version's exact command, authentication, network, and confirmation metadata. - -| Area | Examples | Access | -| --- | --- | --- | -| Diagnostics | version, capabilities, consequences, doctor | Local; optional live auth checks | -| Academic context | calendar, Context v2 live summaries, profile reports, academic snapshots, `academic changes`, one-shot `academic watch` | Public and authenticated reads; guarded local exports | -| TIS | catalog, schedule, grades, exams, TIS-reported degree progress, conservative missing-course report, persistent planning, `tis plan solve/explain/recommend`, local degree audit, live classrooms, iCalendar | CAS login; selection/enrollment writes are confirm-gated | -| Blackboard | courses, roster, course messages, message send preview/apply, discussions, recursive content trees, content type summaries, announcements, deadlines, calendar reads, cross-course grades, per-course and cross-course assignment/attempt overviews, native calendar-link workflow, search, attachment download/sync, attempts, submission | CAS login for REST reads; `bb roster`, `bb messages` / `bb message-participants`, `bb message-send preview/apply`, `bb discussion-groups`, announcement aggregation, `bb tree`, cross-course `bb grades`, cross-course `bb assignments --course ...`, `bb assignments --with-attempts` / `--submission-state`, `bb deadlines --submission-state`, and `bb types` preserve partial failures. Blackboard discussions use the official Learn REST discussion API when the target course exposes it; Original-course forum lists, `bb discussion` thread reads, and `bb discussion-replies` thread-detail reads fall back to the Blackboard HTML discussion board, while group reads and discussion writes that still require the REST surface fail closed with `BLACKBOARD_DISCUSSIONS_UNSUPPORTED`. The native calendar link is a separate stored secret, and local writes are guarded | -| Library and campus services | Primo catalog search/detail, WS programs, eHall booking, library booking, PMS jobs and usage | Public catalog reads plus authenticated reads; booking and queue writes are confirm-gated | -| Research and courses | Crossref/OA papers, NCES browse/filter-options/global-stats/rankings/search/by-code/course/reviews/teacher/stats, SUSTech Online talks and selected handbook search | Public; OA downloads use guarded local paths; NCES and SUSTech Online remain community references only | -| Campus and device context | faculty, resources, transit, Wi-Fi status/events | Public or local | -| Community directory | Selected institutional SUSTech Online contacts with provenance and freshness advisories | Public community source; emergency, financial, personal, dining/chat, and professor-list sections are excluded | - -For the structured TIS-reported `tis degree progress` response, the derived -`tis degree missing` report, and how both differ from local JSON -`tis degree audit`, see -[docs/DEGREE_PROGRESS.md](docs/DEGREE_PROGRESS.md). For the -snapshot save/diff/change/watch workflow, see -[docs/ACADEMIC_SNAPSHOTS.md](docs/ACADEMIC_SNAPSHOTS.md). For the -`tis degree audit` requirements-file format, matching semantics, and current -runtime limits, see [docs/DEGREE_AUDIT.md](docs/DEGREE_AUDIT.md). -For the selected SUSTech Online source scope, provenance fields, freshness -labels, and contact exclusions, see [docs/ONLINE.md](docs/ONLINE.md). - -Remote-state mutations are deliberately limited to these apply commands, all -of which require an exact target plus `--confirm`: - -- `tis enroll apply`, `tis selection apply`, and `tis bid apply` -- `bb submit apply` -- `booking create apply` and `booking cancel apply` -- `lib-booking create apply` and `lib-booking cancel apply` -- `pms upload apply` and `pms delete apply` - -Local state can also change through credential login/logout, persistent -`tis plan` edits, and explicit file outputs such as `profile export`, -`academic snapshot save`, `tis ical --destination`, `papers fetch-oa`, -`bb download`, and `bb sync`. File commands reject unsafe symbolic-link paths -and do not overwrite an existing target unless the command explicitly permits -and requests it. - -## Official campus lectures - -Official university lectures are available without login: +## For AI Assistants + +The CLI exposes its capabilities, output contracts, and mutation consequences +as structured data. Agents should inspect these instead of parsing this README +or guessing flags. ```bash -sustech talks list -sustech talks list --all -sustech talks list --json --pretty -sustech talks search "物理" --jsonl -sustech talks search "物理" --all +sustech capabilities --json +sustech consequences --json +sustech describe "tis enroll apply" --json ``` -These commands read the lecture section of the official -[homepage events page](https://www.sustech.edu.cn/zh/home-events.html), excluding -notices. The default view shows lectures whose Beijing start time is still in -the future, ordered from nearest to furthest. Records with an unparseable time -remain visible under “time to confirm” rather than being silently omitted. -`--all` adds lectures whose advertised start time has passed; it means all -lectures currently displayed on the homepage, not the complete historical -archive. Search matches titles, speakers, venues, and time text, and follows the -same upcoming-by-default behavior. Results include title, speaker, venue, -original time text, a normalized Beijing start time when parseable, timing -classification, detail URL, reference time, and official source/fetch metadata. -The existing `online talks` commands continue to use the community-maintained -SUSTech Online source. The new official commands are CLI-only at present. +Install the bundled Agent Skill: -## Output contract +```bash +npx skills add wormforce/sustech-cli --skill sustech-cli +``` -Course details supplement the existing TIS course search and selection commands: +For a global Codex installation: ```bash -sustech tis courses detail BMEB316 -sustech tis courses detail BIO102B --rwh 2026-2027-1-BIO102B-001 --round bxxk --json +npx skills add wormforce/sustech-cli --skill sustech-cli --global --agent codex ``` -Use an exact course code; if multiple teaching tasks match, the command lists -their identifiers and requires `--rwh`. `--semester` selects the catalog term. -Details include course content, learning outcomes, readings, prerequisite -descriptions and referenced courses, syllabus links, and aggregate enrollment -counts. `--round` adds the relevant selection type's live counts, period, notes -and conflicts. Without it, matching enrolled/cart records can supply counts; -missing counts remain unknown. Optional read failures are reported in `sources` -while the course details remain available. - -Teaching-task and course-library attributes stay separate. Syllabus documents -may describe an older offering; links require TIS authentication and are not -downloaded automatically. Population groups and quota-related counts retain -their original meanings, and are not converted into guaranteed remaining seats. -See [TIS course detail](docs/TIS_COURSE_DETAIL.md) for output fields and examples. +Clients with MCP support can launch the local `sustech-mcp` stdio server. Its +typed surface is intentionally read-only: authenticated data, browser flows, +local writes, and remote mutations stay in the CLI. See [MCP setup](docs/MCP.md). + +## Output That Composes ```bash -# Human-readable text +# Friendly terminal output sustech tis courses search "machine learning" # One versioned JSON envelope sustech tis courses search "machine learning" --json -# One record per line for list commands +# One item per line, followed by a summary sustech tis courses search "machine learning" --jsonl ``` -`--output text|json|jsonl` is the long form, and `--pretty` formats JSON for -review. A successful envelope looks like this: - -```json -{ - "schemaVersion": "1", - "ok": true, - "command": "version", - "data": { - "version": "0.12.1", - "runtime": "node v22.19.0" - } -} -``` - -The process exit status remains authoritative in every mode. See -[docs/OUTPUT.md](docs/OUTPUT.md) for envelope, JSONL, error-code, and -compatibility rules. - -## Credentials - -On a desktop, `sustech auth login` verifies the account and stores the password -in the operating system's native credential store: - -- macOS: Keychain -- Windows: Credential Manager -- Linux desktop: Secret Service via `secret-tool` - -The password is entered through a hidden prompt, is never accepted as a normal -command-line argument, and is never written to the CLI config. If no safe -backend is available, the CLI returns `CREDENTIAL_STORE_UNAVAILABLE` instead of -falling back to plaintext. Linux writes are verified by immediate read-back; -locked collections and broken desktop D-Bus sessions produce distinct safe -remediation in `auth status --json` instead of being reported as an expired -password. - -On macOS, `auth status` checks Keychain item metadata without reading the -password. Credential-helper commands are bounded to five seconds and report -`CREDENTIAL_STORE_TIMEOUT` without an automatic retry. - -```bash -sustech auth login --profile main -sustech auth check --profile main --service bb --json -sustech auth check --service bb --browser --interactive --json -sustech doctor --service bb --live --browser --interactive --json -sustech auth logout --profile main -``` - -Headless runners can use credentials supplied by their own secret manager via -the documented environment variables or credentials file. Service sessions and -cookies remain in memory. See [docs/AUTHENTICATION.md](docs/AUTHENTICATION.md) -for precedence, backend requirements, and non-interactive use. +Every machine-readable response has a stable envelope and the process exit +status remains authoritative. See the [output contract](docs/OUTPUT.md). -For Blackboard only, `auth check` and `doctor --live` also support a read-only -browser-backed verification path with `--browser`, plus `--interactive` when -the user needs to finish CAS manually. That path never accepts browser -credentials in the CLI and never persists browser cookies. +## Safety by Default -Blackboard also exposes a private native calendar subscription link. Treat that -link like a bearer token or password: store it only through stdin, let `show` -mask it by default, and reveal it only with an explicit `--reveal`: +Read commands are easy; writes are deliberately explicit. Remote mutations +follow the same lifecycle: -```bash -# macOS -pbpaste | sustech bb calendar-link set --url-stdin -# Windows PowerShell -Get-Clipboard | sustech bb calendar-link set --url-stdin -sustech bb calendar-link show -sustech bb calendar-link fetch --destination ./blackboard.ics +```text +resolve exact target → preview / preflight → approve → --confirm → read back ``` -The link is validated before storage and kept in the operating-system -credential store under a separate Blackboard-calendar namespace, not in the -credential metadata file. `bb calendar-link fetch` can later refresh that ICS -feed without a fresh CAS login. - -## Guarded workflows - -Every remote mutation follows the same pattern: resolve the exact target, run a -preview or read-only preflight, obtain explicit approval, apply with -`--confirm`, then verify by reading the live state back. - -Enrollment example: +For example: ```bash -sustech tis courses available "machine learning" --round bxxk --json sustech tis enroll preview \ --course-id TIS_INTERNAL_ID --rwh TASK_ID --round bxxk --bid 2 + sustech tis enroll apply \ --course-id TIS_INTERNAL_ID --rwh TASK_ID --round bxxk --bid 2 --confirm ``` -Availability JSON groups lecture/lab rows into credit-deduplicated bundles and -labels the exact `courseId` (`p_id`) and component `rwh` roles. If apply returns -`TIS_SELECTION_OUTCOME_UNKNOWN`, preserve that exact pair and reconcile without -repeating the write: +If a write result is ambiguous, the CLI reports +`DO_NOT_RETRY_AUTOMATICALLY`. It does not trade uncertainty for a duplicate +submission. -```bash -sustech tis selection reconcile enroll \ - --course-id TIS_INTERNAL_ID --rwh TASK_ID --round bxxk --attempts 3 --json -``` +
+Commands that can change remote state -See [docs/SELECTION_CONTRACTS.md](docs/SELECTION_CONTRACTS.md) for bundle, -identifier, bounded reconciliation, and grade-free planning-output contracts. +- `tis enroll apply`, `tis selection apply`, `tis bid apply` +- `bb submit apply`, `bb message-send apply`, + `bb discussion-post apply`, `bb discussion-reply apply` +- `booking create apply`, `booking cancel apply` +- `lib-booking create apply`, `lib-booking cancel apply` +- `pms upload apply`, `pms delete apply` -Blackboard attachment and submission example: +All require an exact target and explicit confirmation. Local exports also use +guarded paths and do not overwrite existing files unless the command documents +and receives an overwrite option. -```bash -sustech bb attachments _8537_1 _629896_1 --json -sustech bb grades --course CS208 --submission-state completed --limit 10 --json -sustech bb assignments --course CS208 --with-attempts --json -sustech bb assignments _8537_1 --with-attempts --json -sustech bb download _8537_1 _629896_1 ATTACHMENT_ID \ - --destination ./homework.pdf -sustech bb attempt-files _8537_1 _2201_1 --json -sustech bb attempt-download _8537_1 _2201_1 FILE_ID \ - --destination ./submitted-homework.pdf - -sustech bb submit preview \ - --course-id _8537_1 --content-id _629896_1 --file homework.pdf -sustech bb submit apply \ - --course-id _8537_1 --content-id _629896_1 --column-id _12345_1 \ - --file homework.pdf --expected-sha256 HASH --confirm -``` +
-`bb submit preview` is authenticated but read-only. It resolves assignment IDs, -checks attempts, due date and upload limit, hashes the file, and emits the exact -apply command. The same preview/confirm/read-back contract applies to TIS -selection and bid changes, booking and library-booking create/cancel actions, -and PMS upload/delete actions. An ambiguous remote write result includes -`DO_NOT_RETRY_AUTOMATICALLY` and must not be retried automatically. - -Booking and library-booking previews now also try to read exact point-in-time -room availability before any write. If the live room calendar, room open-times, -or reservation metadata cannot safely rule out an overlap, preview fails closed -instead of guessing that the slot is free. - -## Academic change tracking - -```bash -sustech academic snapshot save --destination ./before.json --include-blackboard -sustech academic changes before.json after.json -sustech academic watch --state ./academic-state.json --include-blackboard -``` +## Credentials -`academic changes BEFORE AFTER` is the read-only diff command for two saved -snapshots. `academic watch --state PATH` is a one-shot command: it reads live -academic state once, compares it against the existing local state file when -present, reports the changes, and updates that local file. It does not poll, it -does not loop in the background, and it does not write any remote campus state. +`sustech auth login` verifies the account before storage. Passwords are entered +through a hidden prompt, never accepted as ordinary command-line arguments, and +never written to the normal config file. -## Daily context for AI assistants +| Environment | Credential storage | +| --- | --- | +| macOS | Keychain | +| Windows | Credential Manager | +| Linux desktop | Secret Service | +| Headless Linux | Password-encrypted local store when configured | +| Automation | Explicit environment or credentials-file override | ```bash -sustech context --level terse -sustech context --live --level normal -sustech context --live --json -sustech context --live --level verbose +sustech auth status +sustech auth check --service bb --json +sustech doctor --live +sustech auth logout ``` -`context` now has three explicit detail levels: - -- `terse`: date, teaching week and parity, holiday/makeup timetable, and current/next class; only the timetable is requested with `--live` -- `normal` (default): adds the next assignment deadline, recent Blackboard announcement, evaluation, exam, weather and AQI with `--live` -- `verbose`: also retrieves library opening status +Service cookies remain in memory. Browser-backed authentication is +user-completed and ephemeral. Read the full +[authentication guide](docs/AUTHENTICATION.md) before setting up headless or +automated use. -All dates and display times use **Asia/Shanghai**, including on overseas machines. -JSON includes `generatedAt` (snapshot creation), `referenceAt` (the instant used -for class/deadline selection), `timezone`, and the full public `academicDay`. -`schedule.currentClass`, `nextClass`, and `todayClasses` expose ISO timestamps, -periods, locations when available, and `makeupFor` dates. Current and next classes -can appear together; holiday/makeup dates use the same rules as ICS exports. +## Updates -`sourceStatus` distinguishes a successful empty result (`empty`) from unavailable -data (`missing`). `liveSources` adds errors, missing credentials, partial coverage, -and intentionally skipped requests (`not-requested`). An empty result describes -only the successfully retrieved sources; it is not a claim about all university -systems. A failed public calendar fetch does not prevent other available sources -from being returned. - -Weather and air quality include source URLs and upstream `observedAt` timestamps -when supplied. Observations older than three hours are labeled `stale`; absent -timestamps are `unknown`. AQI uses **US EPA** categories, not China's AQI scale. -Public environmental requests time out after eight seconds, and TIS, Blackboard, -and environmental reads run concurrently. - -Without `--live`, only the date/calendar snapshot is requested. Use -`context --date YYYY-MM-DD` for a calendar preview (reference time: noon in -Shanghai); combining a non-today date with `--live` is rejected so today's -observations cannot be mistaken for historical data or forecasts. - -## Library catalog +Interactive terminals check npm for a newer stable release at most once every +24 hours and ask before installing it. CI, redirected commands, JSON, and JSONL +are never interrupted by a prompt. ```bash -sustech library search "graph neural networks" --limit 5 -sustech library detail PC:cdi_proquest_miscellaneous_1901310093 -sustech library search "graph neural networks" --browser --interactive -sustech library detail L:alma991234567890106575 --browser +sustech update +sustech update --yes ``` -`library search` and `library detail` are read-only Primo catalog commands. -`--browser` forces the browser-backed path. If that path redirects to CAS, the -user must complete authentication manually in the browser window. The CLI does -not accept browser credentials, does not solve CAPTCHAs, and does not persist -browser cookies. On some hosts, public Primo HTTP access may still be limited by -runtime TLS behavior; `--browser` is the supported fallback. - -## Conservative course planning +Set `SUSTECH_DISABLE_UPDATE_CHECK=1` to disable automatic checks. -```bash -sustech tis plan recommend CS330 MA203 --round bxxk --path ./tis-plan.json --max 5 -sustech tis plan explain CS330 --round bxxk --path ./tis-plan.json -``` +## Documentation -These commands are read-only planning helpers. They do not add courses, do not -submit selection writes, do not treat NCES as official data, and do not guess -prerequisites. `recommend` ranks candidate sections using the current -selectable-course snapshot, timetable-fit evidence, seat observations, optional -degree-progress or degree-missing data, and optional NCES matches. `explain` -shows the same evidence for one exact course code or RWH. Degree relevance stays -conservative: if the available evidence is ambiguous, the result stays in manual -review instead of being promoted to a definite requirement match. - -## Current limitations - -- Blackboard submission uses a Classic/Original HTTP form with the CAS session - and REST attempt read-back. An individual Original file resubmission passed - live CLI submission and read-back on 2026-09-11. Text submission and the - first-submission 404 fallback remain fixture-tested only. -- Student-submitted attempt files are separate from teacher-provided content - attachments. `bb attempt-files` lists one attempt's files, and - `bb attempt-download` downloads one of them to an explicit local path when - Blackboard exposes a working attempt-file download endpoint for that record; - otherwise the CLI now fails closed with - `BLACKBOARD_ATTEMPT_FILE_UNAVAILABLE`. -- Primo catalog access has both direct and browser-backed paths, but direct - public HTTP access can still depend on the local runtime's TLS behavior. When - in doubt, use `--browser` and complete any CAS step manually. -- Fresh CAS logins for TIS- and Blackboard-backed commands may stop before - password submission with `CAS_INTERACTIVE_CHALLENGE_REQUIRED` when CAS serves - an interactive slide CAPTCHA. The CLI will not bypass that challenge. A - previously stored Blackboard native calendar link can still be fetched - without CAS. -- Blackboard submission remains entirely in the CLI: a fresh form nonce and - exact target are validated before one multipart POST to `uploadAssignment`. - It supports individual Classic/Original file or text submissions with no - Playwright dependency in the write path. Ultra/group assignments and silent - draft resumption are unsupported. An attempts-list 404 becomes empty only - after a matching blank first-submission view form confirms that state. -- Blackboard `bb message-send preview/apply` stays on the official course - message create endpoint, binds apply to the previewed SHA-256 plus exact - recipient IDs, and verifies the created message by Sent-folder read-back. It - is still protocol/fixture-tested only. -- Newly added TIS selection, booking, library-booking, and PMS write paths are - protocol/fixture-tested only. No real account mutation was performed while - building this expansion. -- PMS may require the campus network, and first-time account linking may still - require a browser-side step. -- Reusable service-adapter status can differ from the CLI's wired end-to-end - status. Inspect `sustech services status` and [docs/SERVICES.md](docs/SERVICES.md). - -Module-by-module migration status is tracked in -[docs/MIGRATION.md](docs/MIGRATION.md). Architecture and safety invariants are -documented in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). +| Guide | What it covers | +| --- | --- | +| [Command output](docs/OUTPUT.md) | JSON envelopes, JSONL, exit codes | +| [Authentication](docs/AUTHENTICATION.md) | profiles, credential backends, browser fallback | +| [MCP](docs/MCP.md) | local server setup and read-only boundary | +| [Academic snapshots](docs/ACADEMIC_SNAPSHOTS.md) | save, diff, changes, one-shot watch | +| [Course detail](docs/TIS_COURSE_DETAIL.md) | exact teaching-task selection and enrichment | +| [Degree progress](docs/DEGREE_PROGRESS.md) | official progress, missing courses, local audit | +| [Selection contracts](docs/SELECTION_CONTRACTS.md) | previews, identifiers, reconciliation | +| [Services](docs/SERVICES.md) | implementation and transport status | +| [Architecture](docs/ARCHITECTURE.md) | module boundaries and safety invariants | ## Development @@ -530,20 +270,25 @@ cd sustech-cli npm ci npm run check npm test -npm run build -node dist/cli.js --help ``` -Cross-platform CI runs checks, tests, native credential-store smoke tests where -available, and `npm pack --dry-run` on Ubuntu, macOS, and Windows. Releases use -the tagged, manually triggered Trusted Publishing workflow in -[.github/workflows/publish.yml](.github/workflows/publish.yml); no long-lived npm -token is stored in GitHub. +Cross-platform CI covers Ubuntu, macOS, and Windows on supported Node.js +versions. Releases use npm Trusted Publishing; no long-lived npm token is +stored in GitHub. + +## Project Status + +Upstream university systems change independently and some authenticated flows +can stop at an interactive CAPTCHA. The CLI fails visibly when it cannot +establish reliable state; it does not claim success from an incomplete read. +Current transport notes and known limitations live in +[the service matrix](docs/SERVICES.md). + +## Attribution and License -## Attribution and license +This project is informed by +[`dumixthestpd/sustech_survival`](https://github.com/dumixthestpd/sustech_survival) +and preserves its required copyright notice. -This project is a TypeScript reimplementation informed by -[`dumixthestpd/sustech_survival`](https://github.com/dumixthestpd/sustech_survival). -It preserves that project's required copyright notice and is distributed under -the PolyForm Noncommercial License 1.0.0. See [NOTICE.md](NOTICE.md) and -[LICENSE](LICENSE). +Distributed under the [PolyForm Noncommercial License 1.0.0](LICENSE). See +[NOTICE.md](NOTICE.md) for attribution details. diff --git a/docs/assets/sustech-cli-hero.png b/docs/assets/sustech-cli-hero.png new file mode 100644 index 0000000000000000000000000000000000000000..950f081385bd34cdbc653ab42d7b4263d28a4634 GIT binary patch literal 49952 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 z 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 X@o{%^z5d2ehF +sustech cli + + + + + + + + + + + + + \ No newline at end of file