Command reference
Every command the cs30 tool exposes. General form:
java -jar cs30-1.0-SNAPSHOT.jar <command> [options]
- Every command has
--helpand-h. - Any command that touches the database also accepts
--db-url,--db-user,--db-pass. Leave them off when you run on the server; add them otherwise. See getting started. - The Canvas commands (
course2canvas,submissions2canvas) never touch the database: they read the course through the server over HTTPS, so they take--server <url>and--token <cli token>instead - orCS30_BACKEND_URL/CS30_ADMIN_TOKENin the environment, orcs30.backend.url/cs30.cli.tokenincs30.properties(cs30 doctorasks for both). See Canvas. - Each command below notes what it changes: database, problem pool (git), or read-only.
Dates are yyyy-MM-dd. Date-times are yyyy-MM-ddTHH:mm:ss.
Courses
addcourse — load a course from YAML (database + repos)
Creates or updates every section, lab, problem, and enrollment in the file. Re-running updates existing sections instead of duplicating them. If the repo paths are set, it also initializes the git repos.
| Option | Required | Meaning |
|---|---|---|
--course-file <path> |
yes | Path to the course YAML file |
java -jar cs30-1.0-SNAPSHOT.jar addcourse --course-file=./course.yaml
The YAML format is described in setting up a course.
addlab — add or update one lab (database)
Adds a lab to a course that already exists. Matched by lab number, so re-running updates it. Uses a small lab YAML file (templates/labTemplate.yml is a template).
| Option | Required | Meaning |
|---|---|---|
--lab-file <path> |
yes | Path to the lab YAML file |
java -jar cs30-1.0-SNAPSHOT.jar addlab --lab-file=./lab.yml
changeenddate — change a course’s end date (database)
| Option | Required | Meaning |
|---|---|---|
--course-code <code> |
yes | e.g. CS30 |
--year <int> |
yes | |
--semester <str> |
yes | e.g. Summer |
--section <str> |
yes | a section number, or all |
--end-date <date> |
yes | new end date, yyyy-MM-dd |
java -jar cs30-1.0-SNAPSHOT.jar changeenddate \
--course-code=CS30 --year=2026 --semester=Summer --section=all --end-date=2026-08-15
removecourse — delete a course/section (database)
Removes the section from the database. (It only removes a course that is past its end date.)
| Option | Required | Meaning |
|---|---|---|
--course-code <code> |
yes | |
--year <int> |
yes | |
--semester <str> |
yes | |
--section <str> |
yes | a section number, or all |
java -jar cs30-1.0-SNAPSHOT.jar removecourse \
--course-code=CS30 --year=2024 --semester=Fall --section=1
findcourse — show a course and its students (read-only)
| Option | Required | Meaning |
|---|---|---|
--course-code <code> |
yes | |
--year <int> |
yes | |
--semester <str> |
yes | |
--section <str> |
yes | a section number, or all |
java -jar cs30-1.0-SNAPSHOT.jar findcourse \
--course-code=CS30 --year=2026 --semester=Summer --section=all
Students
addstudent / removestudent — enroll or unenroll one student (database)
| Option | Required | Meaning |
|---|---|---|
--course-code <code> |
yes | |
--year <int> |
yes | |
--semester <str> |
yes | |
--section <int> |
yes | |
--email <email> |
yes | student email |
java -jar cs30-1.0-SNAPSHOT.jar addstudent \
--course-code=CS30 --year=2026 --semester=Summer --section=1 --email=jane@sjsu.edu
java -jar cs30-1.0-SNAPSHOT.jar removestudent \
--course-code=CS30 --year=2026 --semester=Summer --section=1 --email=jane@sjsu.edu
findstudent — list a student’s courses (read-only)
| Option | Required | Meaning |
|---|---|---|
--email <email> |
yes | student email |
java -jar cs30-1.0-SNAPSHOT.jar findstudent --email=jane@sjsu.edu
TAs
setta / removeta — set or clear a section’s TA (database)
| Option | Required | Meaning |
|---|---|---|
--course-code <code> |
yes | |
--year <int> |
yes | |
--semester <str> |
yes | |
--section <int> |
yes | |
--email <email> |
for setta only |
TA email |
java -jar cs30-1.0-SNAPSHOT.jar setta \
--course-code=CS30 --year=2026 --semester=Summer --section=1 --email=ta@sjsu.edu
java -jar cs30-1.0-SNAPSHOT.jar removeta \
--course-code=CS30 --year=2026 --semester=Summer --section=1
Problems in the pool
addproblems writes directly to the problem pool git repo and needs Docker on the machine running the
command. addproblem uploads a ZIP to the server over HTTP — Docker runs on the server, not on your
machine.
addproblem — add one problem via upload
Uploads a problem ZIP to the server. The server extracts it, renders the statement to HTML with Docker,
and commits it to the course’s problem pool repo. Uses the cs30.cli.token already set in your
cs30.properties (shown in the TA dashboard under CLI Token).
| Option | Required | Meaning |
|---|---|---|
--problem-zip <path> |
yes | Path to the problem ZIP file |
--course-code <code> |
yes | Course code, e.g. CS-200 |
--year <n> |
yes | Course year |
--semester <name> |
yes | e.g. Fall or Spring |
java -jar cs30-1.0-SNAPSHOT.jar addproblem \
--problem-zip=./babyshark.zip \
--course-code=CS-200 --year=2026 --semester=Fall
addproblems — add every problem in a folder (problem pool)
Each immediate subfolder of --problems-dir is treated as one problem.
| Option | Required | Meaning |
|---|---|---|
--problems-dir <path> |
yes | Folder containing problem folders |
--git-repo <path> |
yes | The problem pool repo |
java -jar cs30-1.0-SNAPSHOT.jar addproblems \
--problems-dir=./problems --git-repo=/path/to/problems
removeproblem — delete a problem from the pool (problem pool)
| Option | Required | Meaning |
|---|---|---|
--git-repo <path> |
yes | The problem pool repo |
--problem-name <name> |
yes | Folder/name to remove |
java -jar cs30-1.0-SNAPSHOT.jar removeproblem \
--git-repo=/path/to/problems --problem-name=babyshark
Problems in a lab
These change the database (the course’s copy of the problem), not the pool.
updateproblemlanguage — change a problem’s language in one lab (database)
| Option | Required | Meaning |
|---|---|---|
--course-code <code> |
yes | |
--year <int> |
yes | |
--semester <str> |
yes | |
--section <int> |
yes | |
--lab <int> |
yes | lab number |
--problem-name <name> |
yes | |
--language <str> |
yes | e.g. python, java, cpp |
java -jar cs30-1.0-SNAPSHOT.jar updateproblemlanguage \
--course-code=CS30 --year=2026 --semester=Summer --section=1 --lab=1 \
--problem-name=babyshark --language=java
cancellab — remove a lab and its problems from the course (database)
Deletes the lab from the course. The problem pool is not touched.
| Option | Required | Meaning |
|---|---|---|
--course-code <code> |
yes | |
--year <int> |
yes | |
--semester <str> |
yes | |
--section <int> |
yes | |
--lab <int> |
yes | lab number to cancel |
java -jar cs30-1.0-SNAPSHOT.jar cancellab \
--course-code=CS30 --year=2026 --semester=Summer --section=1 --lab=4
Checks
validatecourse — confirm every referenced problem exists (read-only)
Checks each problem the course references against the problem pool and lists any that are missing. Exits with an error if something is missing. Run it before a lab opens.
| Option | Required | Meaning |
|---|---|---|
--course-code <code> |
yes | |
--year <int> |
yes | |
--semester <str> |
yes | |
--section <str> |
yes | a section number, or all |
java -jar cs30-1.0-SNAPSHOT.jar validatecourse \
--course-code=CS30 --year=2026 --semester=Summer --section=all
Canvas
These push a lab into Canvas. They change Canvas, never the database or the problem pool.
They read the cs30 side - the lab window, its problems, the roster, and each student’s best submission - through the server rather than the database, so they run from any machine that can reach the server. Tell them where it is and which CLI token to use:
export CS30_BACKEND_URL='https://sjsu.cs30.app' # or --server, or cs30.backend.url in cs30.properties
export CS30_ADMIN_TOKEN='...' # or --token, or cs30.cli.token in cs30.properties
The admin token works for every course. A TA’s own token works for the section that TA is assigned to
and is refused for any other. --db-url and friends are not needed and are ignored.
Both default to a dry run: they print what they would do and make no changes. Add --no-dryrun to
apply. Because one command reads a cs30 course and writes a Canvas course, the cs30 options are
prefixed --cs30- and the Canvas ones --canvas-, so it is always clear which system an option
refers to.
Set the Canvas instance and an access token before running either. The token is a secret, so keep it in the environment and out of the configuration file:
export CANVAS_TOKEN='12~...' # Canvas: Account > Settings > New Access Token
export CANVAS_URL='https://sjsu.instructure.com' # only if your instance differs from the default
Both can also live in cs30.properties as canvas.url / canvas.token (cs30 doctor asks for
them, and its canvas check confirms Canvas accepts the token); the environment takes precedence.
The token carries your own Canvas permissions, so you need teacher or TA rights on the course.
course2canvas: create Canvas assignments for a lab
One assignment per problem in the lab. The name is LAB plus the lab number padded to two digits,
with the first word of the problem’s note appended when it has one:
| problem note | assignment |
|---|---|
| (none) | LAB01 |
Bonus problems |
LAB01-Bonus |
Extra credit |
LAB01-Extra |
This is the convention used when assignments are created in Canvas by hand, so the commands find
pre-created assignments rather than making duplicates. Matching ignores case and surrounding spaces.
Existing assignments are left alone unless --force.
The note is the only thing separating one problem’s assignment from another’s, so at most one problem per lab may have no note. If two problems resolve to the same name the command stops and names them, rather than syncing both to one assignment.
Every assignment is created with 100 points, and dates come from the lab window: unlock_at from
the start, due_at and lock_at from the end.
| Option | Required | Meaning |
|---|---|---|
--cs30-course-code <code> |
yes | cs30 course to read |
--cs30-year <int> |
yes | |
--cs30-semester <str> |
yes | |
--cs30-section <int> |
yes | |
--cs30-lab <int> |
yes | Lab whose problems become assignments |
--canvas-course <id or name> |
yes | Canvas course id, or a name/code fragment that matches exactly one course; a miss lists the active courses |
--canvas-section <name> |
no | Scope the dates to one Canvas section, for a course that holds several |
--assignment-group <name> |
no | Canvas assignment group, created if missing (default Labs) |
--rubric <title> |
no | Attach an existing Canvas rubric, matched by title |
--dryrun / --no-dryrun |
no | Dry run is the default |
--force / --no-force |
no | Update assignments that already exist (default false) |
java -jar cs30-1.0-SNAPSHOT.jar course2canvas \
--cs30-course-code=CS30 --cs30-year=2026 --cs30-semester=Spring \
--cs30-section=1 --cs30-lab=1 \
--canvas-course=12345 --rubric="Lab Rubric"
The rubric must already exist in the Canvas course; this never creates one, and a title that matches nothing fails with the list of rubrics it can see. It is attached for grading, which means Canvas replaces the assignment’s points with the rubric’s own total. If you want the assignment to stay at 100, make the rubric total 100.
The rubric is attached when an assignment is created, and again on --force. A plain re-run that
skips an existing assignment does not touch its rubric.
submissions2canvas: mirror best submissions as comments
For each enrolled student, reads their best submission for every problem in the lab and posts it as a submission comment. No grade is entered: the score is stated in the comment so the professor can grade manually.
Assignments must already exist, either created by course2canvas or by hand in Canvas. This looks
them up by the same derived name (LAB01 style, plus the note’s first word), and for any it
cannot find it warns and lists the assignment names the course does have, so a naming mismatch is
easy to spot.
| Option | Required | Meaning |
|---|---|---|
--cs30-course-code <code> |
yes | cs30 course code, or a fragment of one that matches exactly one course |
--cs30-year <int> |
no | Narrows the match |
--cs30-semester <str> |
no | Narrows the match; may be a fragment (fa is Fall) |
--cs30-section <int> |
no | Narrows the match |
--cs30-lab <int> |
yes | Lab whose submissions are mirrored |
--canvas-course <id or name> |
yes | Canvas course id, or a name/code fragment that matches exactly one course |
--dryrun / --no-dryrun |
no | Dry run is the default |
--force-comment / --no-force-comment |
no | Post even when the same submission was already mirrored (default false) |
java -jar cs30-1.0-SNAPSHOT.jar submissions2canvas \
--cs30-course-code=CS30 --cs30-year=2026 --cs30-semester=Spring \
--cs30-section=1 --cs30-lab=1 --canvas-course=12345
Both courses can be named by a fragment, so the short form is usually enough:
java -jar cs30-1.0-SNAPSHOT.jar submissions2canvas \
--cs30-course-code=cs30 --cs30-lab=1 --canvas-course="cs 30"
--cs30-course-code is matched by the server, case-insensitively, as a substring of the code, with an
exact code winning outright (CS30 resolves even when CS30A exists). --cs30-year,
--cs30-semester and --cs30-section only narrow the candidates, so they are needed only when the
code alone fits more than one course. The search covers the courses your token may read: every course
with the admin token, only your own sections with a TA token. --canvas-course works the same way
against the Canvas course name and course code, unless it is all digits, in which case it is the
course id. Both print the course they picked before doing anything.
A fragment that fits several courses is an error that lists them, so the sync never guesses; one that fits nothing lists the active courses to pick from (cs30 courses that have not ended, Canvas courses that are not concluded). Canvas courses are listed with their term and state, so same-named courses from different semesters can be told apart, and the course id always works.
A posted comment looks like:
Best submission for pascalmagic: 1/33 test cases passed, submitted 2026-08-06T04-10-12 UTC.
submission-2026-08-06T04-10-12.cpp
<the source, inlined when under 8 KB>
Re-runs are cheap. A student is skipped when a comment already records a submission at least as new,
so only students who submitted again get a new comment. --force-comment posts regardless, which
adds another comment rather than editing the previous one, since Canvas comments cannot be
edited through the API.
Students are matched to Canvas users by email, falling back to the Canvas login id. Anyone with no matching Canvas user, or with no submission, is counted and reported rather than treated as an error. A student whose email cannot match — typically a Canvas account under a personal address — can be mapped by student override instead.
Student overrides
Sometimes the email match cannot work: a student’s Canvas account (and often their cs30 enrollment
with it) is under a personal address. An override maps a cs30 enrollment email to the student’s
Canvas student id (the login/SIS id), and submissions2canvas then matches that student by id
instead of email. An overridden email is matched only through its id — a stale override is
reported as “no Canvas user”, never silently ignored.
Overrides live in their own table on the server, so they survive addcourse re-importing the
rosters, and the commands run remotely like the other Canvas commands (--server/--token, or the
same configuration). Listing works with the admin or a TA token. The admin can add or remove any
override; a TA only one for a student enrolled in a section they are the TA of.
addoverride — map an email to a Canvas student id
Re-running with a corrected id updates the existing override.
| Option | Required | Meaning |
|---|---|---|
--email <email> |
yes | cs30 enrollment email |
--student-id <id> |
yes | Canvas student id (login or SIS id) |
java -jar cs30-1.0-SNAPSHOT.jar addoverride --email=student@example.com --student-id=012345678
removeoverride — remove an override
| Option | Required | Meaning |
|---|---|---|
--email <email> |
yes | cs30 enrollment email of the override |
java -jar cs30-1.0-SNAPSHOT.jar removeoverride --email=student@example.com
listoverrides — list the overrides
java -jar cs30-1.0-SNAPSHOT.jar listoverrides