Docs · CLI reference

CLI reference

sboot — learn by building. One static binary. The graded commands run from anywhere inside your course repo — it finds the workspace by walking up to sboot.toml — and the rest (courses, login, start, resume, bare sboot) work from anywhere at all.

sboot                       where you are and what's next
sboot start [course]        create a course workspace and its git repo, named
                            after what you finish with (./word-game-sb/)
sboot test [stage]          practice: grade the current lab locally
sboot hint [stage] [check]  a hint for a failing check — one hint deeper per run
sboot explain [check]       the AI tutor, fed your last run's evidence
sboot submit [stage]        official: check locally first, then upload for the
                            server grade
sboot courses               the catalog + your progress
sboot login                 connect this machine to your account
sboot repo                  put this workspace on GitHub — a private repo, pushed (via gh)
sboot resume <path|url>     pick a course back up on this machine (clones a git
                            URL first)

That is sboot help. sboot help --all adds the rest — reveal, logout, whoami, debug, fetch, where, version — plus every flag and environment variable.

The stage argument is optional

test, submit, hint and debug all default to your current lab: the first live lab the server has not recorded as complete (server truth when online, the cached answer offline). The choice is never silent — the command prints it, and names the override:

grading 02-glass-cockpit — "The glass cockpit"   # your current lab; sboot test <stage> overrides

An explicit stage always wins, and must start with its lab number: 02-glass-cockpit, or just 02; not glass-cockpit.

Commands

sboot (no arguments)

The orientation screen, exit 0 in every state. Inside a workspace: the course, every lab with its state (✓ verified · current · locked · not published yet), your last practice score on the current one, and the next action.

Kernel in Rust: Own the Machine — kernel-in-rust · 0 of 2 live labs verified

▸ 00 welcome            up next
  01 first-light        locked until 00 completes
  ⋮  (24-lab arc — labs open in order)

next:  sboot test               # grades 00-welcome
stuck: sboot hint · read: https://sourceboot.com/courses/kernel-in-rust/stages/00-welcome

Outside a workspace it prints who you are connected as, the courses you have started, and how to start another. Offline it answers from the last sync and says so.

sboot start [course] [--dir NAME]

Creates the workspace with the course scaffold: your source tree, the pinned build config, a README, an MIT LICENSE, a .gitignore, and sboot.toml (the file that tells sboot which course the repo is for). In the same run it fetches the first lab's tests and the grading engine into the SourceBoot data directory — outside your repo — and ends by naming the first lab, the command that grades it, and its page on the site.

The folder is named after what you finish with, not after the course id: word-game-sb for Rust for Beginners, kernel-sb for the kernel courses, sqlite-reader-sb for Rust for Systems. A course that names no such artifact falls back to its course id (os-rust). --dir NAME overrides either.

It is local-only — it never speaks to GitHub, so it cannot fail there. After unpacking it runs git init inside the new folder, asks once for the name and email git stamps on commits if you have not set them anywhere (saved --global, so no course asks again), makes the first commit, and prints one line: "when you want it on GitHub: sboot repo." With no terminal to ask on, it leaves the commit undone rather than guessing an identity, and prints the three commands that finish it.

Run it again and it repairs. In or beside a workspace for that same course it re-fetches the scaffold, copies across only the paths that are missing, and prints what came back — a file you have edited is not compared, not backed up and not touched. A directory that is not empty and not one of ours is still refused, naming both ways out (--dir, and sboot resume).

With no argument — and no SBOOT_COURSE in the environment — it prints the catalog and how to pick, and creates nothing.

sboot test [stage]

The practice loop: resolve the lab's tests (fetching them on first use), run the course's build, run the result, grade it against the lab's published checks. Build output streams live; the verdict is [PASS]/[FAIL] per check with points and evidence, then score: N/M. When you are online the run is recorded to your account as a practice run — the score and per-check verdicts, never your source — and shows up on /home and on the lab's page. Practice never completes a lab, and an offline or unrecorded run costs you nothing.

A failing run ends by pointing at both help surfaces: sboot hint, and the lab page's #stuck section.

sboot hint [stage] [check-id]

A hint for a failing check, on courses that publish hints. Bare sboot hint targets the first check your last sboot test recorded as failing (so run sboot test first); naming a check id targets that one. Each ask for the same check goes one hint deeper — the header counts them, [hint 1 of 2 · <check-id>], and the last one is the deeper debug ladder — and it only ever goes forward. It works offline: it reads the hints shipped with the lab's tests and your own local state. A course with no published hints says so and exits cleanly; so does a check that is already passing.

If your last sboot test stopped at the build, no check ran, so sboot hint works from the compiler's first error instead. The first ask quotes that error (its code, message and file:line). Each later ask for the same error goes one hint deeper, into what the lab's hints say about it: the likely cause behind that error code, then the lab's compile hints. A different first error starts again at the top. When no written hint covers the error, it says so and still quotes the error.

When you are online, each hint shown sends one line of usage data, so we can see where people get stuck: the course, the lab, the check (or build), how deep the hint was and which kind it was. Never your code, your compiler's output or a file path. SBOOT_OFFLINE=1 sends nothing.

The last written hint hands off to the next step up the ladder — sboot explain, and past that sboot reveal — and to the lab page's Stuck? section, which mirrors the same ladder and hosts the chat.

sboot explain [stage] [check-id] [--here] [-m "question"]

The step above the written hints: the AI tutor, with your last run's evidence already attached. Bare sboot explain targets the same check sboot hint would.

By default it prints — and opens, where a browser is available — the lab page's Stuck? chat with your check in the link. The chat lives there because it keeps context across turns and can render code.

--here answers in the terminal instead: one question, one streamed reply, no browser. -m / --message supplies your own question; without it the CLI asks for direction on the check you are stuck on. What it sends is your question plus what your last sboot test recorded — the lab, the failing check ids and the score, or the reason a run never reached a check. It does not send your source.

The tutor is metered per day by plan. When the allowance is spent, the refusal says so with the numbers and points back at sboot hint, which is never metered. explain needs the network in both halves; the hint ladder does not.

sboot reveal [stage] [--yes]

The end of the ladder: the course's own solution for a lab, in two steps — the module skeleton first, and the full reference only on a later run. The server decides whether a lab can be revealed and in which order; the CLI asks first and tells you the price before anything is fetched.

A reveal marks that lab solution-assisted on your record. The mark is an annotation on your own progress record — it never blocks completing the lab, and it changes no score. You are told this in the confirm prompt, before the solution arrives, and again after.

The files are printed with a header each and saved beside your run (under the scratch directory sboot where prints), never into your own tree — it is a copy to read next to your code, not a patch applied to it. --yes answers the prompt for a non-interactive shell; without a terminal and without --yes, reveal refuses rather than reveal silently.

reveal is online by nature: the solution is fetched when you ask for it and is never shipped with the tests, so offline it refuses — and says plainly that nothing is wrong with your setup.

sboot submit [stage] [--force]

The official grade, in four moves: ask the platform whether the stage is open to you (before spending a build on it) → run the same local check sboot test runs — one build, one run → on a local pass, upload that run's captured result plus your source tree (source only: target/ and .git are excluded) → our server judges it and answers with the verdict in the same command.

A passing submission is submitted for review, not finished: the last line is a link to that submission's review page, and the Complete button there is what records the lab and unlocks the next one.

A local fail stops the submit: nothing is created, nothing is sent.

--force / -f submits anyway after a failed local run — for when you think the local grader is wrong, or you want the failed attempt on the record. What --force cannot do is submit with no local run at all: since the official grade is computed from the run your machine makes, a machine that cannot run sboot test cannot submit either.

sboot courses

The catalog and your progress across it — started courses first, then the rest with their live-lab and free-lab counts. Cached on every run, so it keeps answering offline after one sync; it says when what you are reading is the cached copy.

sboot login

Connects this machine to your account. It asks the platform for a pairing code, prints the code and an /account/connect link (and opens it if a browser is available), and waits; one click in the browser you are already signed in with hands the CLI a token, which it stores 0600 in its own config directory. Nothing is copied and pasted.

SBOOT_TOKEN in your environment always overrides the stored login — that is the fallback path for CI and headless machines, and sboot login says so if the variable is set. A stored login for a different platform than the one you are talking to is reported and not used.

sboot logout

Removes the stored login from this machine. If SBOOT_TOKEN is also set it says so — that one still works until you unset it.

sboot whoami

Asks the platform who the current credential resolves to, and prints the handle plus which source the token came from (SBOOT_TOKEN, the stored login, or the local development default). It is a live check, not a read of the file: a revoked token answers honestly.

sboot repo [--name NAME]

The remote, and only the remote — sboot start has already made the local repo and its first commit. The default repo name is the folder's: word-game-sb, kernel-sb; a course that names no artifact still gets the signature on GitHub (os-rust-sb), and --name overrides. It is GitHub's own CLI that does the work, so we hold no credential at any point, and nothing in the course depends on running this.

What it does depends on what your machine has:

  • gh installed and signed in — it names the account and the repo, asks once, and runs gh repo create <name> --private --source . --push. On success: ✓ pushed → github.com/<you>/<name>.
  • gh installed, not signed in — it runs gh auth login for you, as a child of this terminal (that is gh's own browser flow, storing gh's own credential), then carries straight on to the create and push.
  • no gh — two ways forward. Either the install line for this machine — checked, not assumed: brew install gh where brew really is installed (Homebrew's installer first where it is not), sudo apt install gh / sudo dnf install gh by which one you have, winget install --id GitHub.cli on Windows — which you run yourself, since it wants an admin password we should never be prompting for; or the manual path: a prefilled github.com/new link, then cd into the workspace first, git remote add origin …, git push -u origin main, plus the sentence that decides whether that push works — GitHub will ask for a token, not your password — and a link to GitHub's own page about making one.

It never touches an existing remote: with one set it says so and stops. Nothing here is a prerequisite for anything, and declining costs nothing.

While a workspace has no remote, one line points at this command — after a passing sboot submit, when there is finally something worth showing, and on sboot test at most once a day:

your work is committed here but has no home on GitHub yet — sboot repo

It is silent when git is missing, when there is no repo, and forever once a remote exists.

sboot resume <path|git-url>

Picking a course back up on a machine that has never seen it — a new laptop, a rebuilt one, a clone of the repo you pushed. Given a git URL it clones first (plain git clone, with whatever credential your machine already uses), into a folder named after the repo; given a path it takes the workspace as it is.

Then, in order: check that it really is a workspace (sboot.toml), fetch the course's tests and the grading engine — the half a clone is always missing, because they were never in the repo — and grade the first lab. That last step is the point: the question on a rebuilt machine is not "are the files here" but "does this still work", and only a verdict answers it. The command's exit code is that lab's.

It ends by naming your workspace and sboot test, and offers sboot repo if this copy has no remote. A directory with no sboot.toml is refused — and if exactly one workspace sits one level down, the refusal names it.

sboot where

Prints where everything lives: your repo, the cached tests and their spec version, the grading engine (a separate, checksum-verified program fetched with the tests), the course's build tooling, the scratch directory grading runs in, and your state file. Reads only local state — works offline.

sboot debug [stage]

Boots the stage under QEMU, frozen, waiting for a debugger on :1234 — attach gdb and go. Interactive and long-running; nothing is graded or recorded. It hands off to the course's own build tooling, so it works on the courses whose tooling has a debug mode (os-rust, x86-64-essentials) and not on those that don't (kernel-in-rust).

sboot fetch [course]

Downloads the current lab's tests deliberately — to refresh them, or to warm the cache before losing your connection. sboot test already fetches what it needs on its own; fetch exists for doing it on purpose. Prints the spec version, how many labs are cached, and where.

sboot version

Prints the version (also --version).

Flags

Flag Where What it does
--force, -f submit Submit even though the local check failed.
--here explain Answer in this terminal instead of opening the lab page's chat.
--message, -m explain Your own question, instead of the one the CLI composes.
--json bare sboot, test, submit Machine-readable object on stdout; the human rendering moves to stderr.
--yes, -y start, repo, reveal Answer yes to every prompt. (Never the identity question: an email cannot be guessed.)
--dir start Unpack into this folder instead of the course's own.
--name repo Create a repo of this name instead of the default.
--no-color anywhere No ANSI colour (NO_COLOR is honored too).
--help, -h anywhere The short help; sboot help --all for everything.
-- anywhere End of options — everything after it is positional.

Environment variables

Variable What it does
SBOOT_API_URL Platform URL. Default https://sourceboot.com.
SBOOT_TOKEN Your API token. Overrides a stored sboot login. The built-in default is a local development value that works against no real deployment.
SBOOT_COURSE Course id for sboot start/fetch outside a repo. Inside a repo the course comes from sboot.toml.
SBOOT_COURSE_DIR Your course repo, if you don't want the walk-up-from-cwd lookup.
SBOOT_CACHE_DIR Where tests + grading engine are cached. Default: the OS data dir (~/Library/Application Support/sboot on macOS, ~/.local/share/sboot on Linux).
SBOOT_STATE_DIR Where the stored login and state.json (how deep you are in each check's hints, last verdicts) live. Default: the OS config dir.
SBOOT_OFFLINE Set to anything to skip every network call. Cached tests still grade.
SBOOT_NO_UPDATE_CHECK Suppress the new-version notice line.
SBOOT_DEBUG Print the CLI's own diagnostics on stderr.

The install script additionally reads SBOOT_INSTALL_DIR (default ~/.local/bin) and SBOOT_VERSION (install a specific release — see Troubleshooting).

Exit codes

  • 0 — success: full marks on a graded run, or the command did its job.
  • 1 — a graded run that ran and came up short: the build failed, or one or more checks failed (including an official grade below full marks). Also the smaller "asked and answered no" cases — whoami on a credential the platform rejects, hint on a check nobody has written one for.
  • 2 — the run never got to a verdict: usage error, no workspace, missing tool, a lab that is not open to you, a refused submission, hint before any test, or a platform problem.

The distinction that matters: sboot separates your code failed (exit 1) from something else is wrong (exit 2), so a toolchain problem is never recorded as a bad practice run.

Offline

sboot test and sboot hint work with no network at all once a lab's tests are cached — grading is local. Bare sboot and sboot courses answer from the last sync and label it. What you lose offline: fetching new labs, the practice-run record (the run still grades; it is just not recorded — that's fine, it's practice), the one-line hint-usage record sboot hint sends, sboot login/whoami, sboot explain --here and sboot reveal (both run on our side by definition), and sboot submit, which by definition needs the server. SBOOT_OFFLINE=1 makes the no-network promise explicit: every network call is skipped, including the update check.

Update notices

When a newer release exists, sboot prints one line after the verdict, at most once a day:

sboot 0.11.0 → 0.12.0 available · update: curl -fsSL https://sourceboot.com/install.sh | sh · notes: github.com/sourceboot/sboot/releases

Updating is re-running that installer, which replaces the binary in place. There is no sboot upgrade verb — the install command is the whole mechanism. SBOOT_NO_UPDATE_CHECK silences the line; SBOOT_OFFLINE silences it too.

On Windows the line names the Windows way instead, because there is no shell script to pipe: download sboot-windows-amd64.exe from github.com/sourceboot/sboot/releases and replace your sboot.exe.

A second kind of line shares that channel: a notice from the platform — a renamed course, say. Each distinct notice prints once and then waits a day before repeating, so a standing announcement never rides along on every command; one you have not seen yet is never held back.

"Your CLI is too old for this course's tests"

Lab tests are versioned, and a course can declare a minimum CLI version for them. If your binary is older, sboot refuses to grade rather than guess:

02-glass-cockpit's tests need sboot 0.3.0 or newer — this is sboot 0.2.0.
  Update: curl -fsSL https://sourceboot.com/install.sh | sh · notes: github.com/sourceboot/sboot/releases
  Nothing is wrong with your code. This binary would have to guess at part of
  these tests, and a guessed grade is worse than none

The fix is the printed install command. Offline this rule cannot fire: an old binary keeps grading against the spec it already has, which is a matched pair. To install a specific version instead of the latest, see Troubleshooting.

One thing an old binary gets wrong, and cannot be patched to stop getting wrong: a sboot from 0.3.x or earlier prints stage complete! on a passing submit. It isn't — since the review flow landed, a pass is submitted for review and the lab is recorded when you press Complete on the review page. If you see that line, update; either way the site is the truth about what you have completed.