Command Index

The complete reference for every shell command — name, aliases, flags, a one-line description, the exact usage string, and the command's full description (its function docstring). This is the long form of the User Guide's command catalogue, which links here per command.

The per-command sections below are generated from the live command registry by the update-docs command — do not edit them by hand. To change a command's detailed description, edit its function docstring in the source and run update-docs; also run it after changing any command's name, alias, help text, or usage.

Legend

Each command entry opens with an availability line — profiles: …. Profiles are those whose permissions satisfy the command's requires= grants under authorizations.json — reported from the live registry (see the generated authorizations.md audit table). Authorization is by profile only; the channel (terminal / web) is not an axis. requires is mandatory, so every command declares its floor. See the web server guide for the authorization model.

A command's flags line lists the behaviours marked by these glyphs — the same ones a command's ? panel carries at the prompt. The table is generated from the glyph constants themselves by update-docs — do not edit it by hand:

glyph meaning
⚑ the least profile that may run it — every rung above may too
❏ takes an optional problem number (defaults to the current problem)
✎ asks for anything you leave out
» supports --silent to suppress its incidental output

In a usage string: <required>, [optional], a|b|c is a choice, key=value sets a named parameter, --flag / --no-flag toggle a boolean, and ... marks a parameter that accepts repetition.


Commands

command catalogue (click to expand) — each name jumps to its entry below

In every shell — solver, the web shell and solver-host:

Command Aliases Requires Description
! sh, bash contributor Run a shell command from the shell, returning its exit code.
? help reader List every command, or show detailed help for one command.
benchmark — contributor Measure and record the execution time of the problem's solutions.
blog — reader List a blog article's declared tags and what each one maps to.
blogs — reader List a problem's tags and the blog articles that cover them.
check-commands — admin Report every command docstring that breaks the documented standard.
claude — contributor Run Claude Code interactively in the repository root, returning its exit code.
claude-api — contributor Generate one of a problem's solution artifacts through the Claude API.
claude-batch — maintainer Bulk-tag a wave of problems in one Message Batches job, at half price.
claude-blog — maintainer Write (or flesh out) a blog article via the claude-euler-blogger skill.
claude-login — contributor Sign Claude Code in, then report whether it is, returning 0 only when signed in.
claude-solve — contributor Run Claude Code over a problem's solution files, via a skill.
clear cls reader Clear the terminal screen and scrollback, then succeed.
compile-c compile contributor Compile every C solution for the problem into a runnable binary.
costs — contributor Print what this session's AI tokens have cost, per model.
create-blog — maintainer Seed a curated cross-cutting blog page.
echo — reader Print the given text to the console, then succeed.
edit ed contributor Open a solution file in the web code editor.
evaluate eval contributor Evaluate a problem's solutions against its test cases.
gh-merge merge maintainer List the open pull requests, or rebase-merge one onto master.
git-audit audit contributor Audit what git actually stores, across the whole tracked tree.
git-commit commit contributor Stage and commit the named targets — the one commit verb.
git-filter filter reader Report or wire the transparent encryption filter for solutions/private.
git-hooks hooks contributor Run the git pre-commit and (simulated) pre-push checks on demand.
git-identity identity contributor Configure your git identity and push credential from your GitHub login.
git-publish publish maintainer Publish changed files for named targets to the remote repository.
git-push push contributor Push the current branch to origin as yourself, then open its pull request.
git-reset reset reader Soft-reset your branch to origin/master — un-commit, keep every change.
git-status status reader Display the sync state between the local branch and origin/master.
git-sync sync reader Bring the local repository in sync with origin/master.
host-unlock — admin Take the mailed block from host-authorize and unlock this machine.
key-export — admin Print the master key sealed to a machine's public key, to carry there yourself.
key-reconstruct — reader Unwrap the half you were sent, complete it from the repository, store the key.
key-rekey rekey admin Rotate the master key and re-encrypt every private file under the new one.
lint — contributor Lint the problem's solution files, optionally auto-fixing them.
ls — reader List the files in a problem's solution directory.
mark mark-solved contributor Mark the current problem as solved — once its results confirm it.
new — contributor Generate new solution and/or test-case files for the problem.
pause — reader Wait for the user to press Enter before the block carries on.
pip-upgrade upgrade admin Upgrade packages in the current venv for the given dependency groups.
problems — reader Print a list of problems and their count.
progress — reader Print overall progress through the Euler problems.
random — reader Return a random whole number from min to max, both included, as the exit code.
results — reader List the recorded results for a problem.
search find reader Search the solution stack for a case-insensitive regular expression.
show open, view reader Open a problem's page, or any site page, in a browser or the web viewer panel.
sleep — reader Wait the given number of seconds before the block carries on.
summary — maintainer Refresh the solved/unsolved state from your Project Euler progress page.
sys-setup install admin Install or uninstall a system resource.
tags — reader Report over the central tag vocabulary (blogs/tags.json).
test-cases — reader List a problem's test cases.
tool-setup — contributor Install, uninstall or reinstall a per-user tool in your own home.
update-docs — admin Rebuild the registry-generated blocks in the docs/ guides and the README.
update-models — admin Refresh the model catalogue and its pricing.
update-presentation-data — maintainer Refresh the presentation deck's data from the progress file.
update-tags — contributor The glue for the double-entry tag graph.
update-usd-rate — maintainer Refresh the USD→EUR rate used to report API costs.
user — reader Show the solver user, the current identity, and whether it can decrypt.
vault — reader Encrypt this user's id + env at rest under a password-derived vault key.
version — reader Print the installed build version, plus live git detail of the clone.

On the web host only — loaded by the web shell and solver-host, which have the message spool, the roster and the mail relay these need:

Command Aliases Requires Description
host-authorize — admin Mail the master key, sealed to one machine's public key — the off-host grant.
key-split — maintainer Send someone half the master key, sealed to their public key.
logout — reader Sign out of the web session, after a short countdown the notice can skip.
logout-all — admin Sign every web user out, after a countdown that running work can extend.
msg messages reader Read and send messages: what is waiting for you, and what it asks you to do.
user-authorize authorize maintainer Record someone's public key and send them half the master key.
users — admin Administer accounts on the authorization map + the auth service.

Command: ! (sh, bash)

Run a shell command from the shell, returning its exit code.

  • ⚑ needs contributor or above.

Four forms escape into an interactive session that takes over the terminal:

`! sh` / `! bash`       an interactive bash subshell, in the solution dir
`! py` / `! python`     an interactive Python interpreter, in the repo root
`! claude [prompt]`     Claude Code, in the repo root (also plain `claude`)
`! ssh <host> [...]`    an ssh session (or remote command), in the solution dir

Any other command (! ls, ! git diff, …) runs non-interactively in the current problem's solution directory — so paths are relative to that problem's files — with its output streamed through the shell (so solver -s can log it). After the command finishes, the problem specials are refreshed (↻) in case it changed the files.

Aliased as sh and bash, so sh <command> is shorthand for ! <command>.

usage

! <command> [args]...
! sh → escape to a bash shell.
! py → escape to a python interpreter.

arguments

argument description
*args The command line to run, token by token — quoted before it reaches bash, so ! git log --oneline arrives intact. With none, prints its own usage and fails.

Defined in solver.shell.bash._bash.


Command: ? (help)

List every command, or show detailed help for one command.

  • ⚑ needs reader or above.

With no argument, prints the catalogue: command, aliases, the profile it needs, and its description. Nothing there is abbreviated to a glyph — the facts one would stand for (the problem a command takes, the --silent it supports) are spelled out in words in the command's own panel, one ? <command> away.

With a command name or alias, prints that command's own panel — its description, the facts about it (the profile floor, and whether it takes a problem or supports --silent), the prose and free sections of its docstring, its usage, and a row per argument it accepts. It renders the same HelpModel that update-docs publishes as docs/commands-index.md, so the guide and the prompt cannot disagree; docs/developer-guide.md §3.8 is the docstring standard behind both. Returns non-zero if the named command is unknown.

Aliased as help.

usage

? [command]

arguments

argument description
*args The command name or alias to describe. With none, lists every command.

Defined in solver.shell.builtins._help.


Command: benchmark

Measure and record the execution time of the problem's solutions.

  • ⚑ needs contributor or above.
  • ❏ uses/sets current problem.
  • » supports --silent to suppress output.

Like eval, runs every solution against the chosen test-case categories, but always records the timings to results.json and repeats each case an adaptive number of times (see "Repeats") instead of running once. Run eval first to confirm correctness, then benchmark to measure; categories default to all three ('dev', 'main', 'extra').

usage

benchmark
[problem=<n>] (default current)
[all|dev|main|extra ...]
[clean=false|--no-clean]
[timeout=<float>|none] (default None)
[disable_timeout=true|--disable-timeout]
[lang=*|py|c] (default *)
[solution_index=<int>|none] (default None)
[reset=true|--reset]
[verbose=true|--verbose]
[silent=true|--silent]

arguments

argument description
problem The problem whose solutions to benchmark.
*categories Test-case categories to include: 'dev', 'main', 'extra', or 'all' (which expands to all three). Defaults to all three.
clean When False, reuse up-to-date build output from previous compilations. Defaults to True.
timeout Per-run timeout in seconds for a solution's execution. None uses the configured default. Defaults to None.
disable_timeout Run without a timeout, and only once per test case — bypassing the adaptive repeat count above. Defaults to False.
lang Which language to benchmark: '', 'py' or 'c'. Defaults to ''.
solution_index Benchmark only this solution index. None benchmarks every solution. Defaults to None.
reset Replace any existing persisted results with this run, on a clean completion; an interrupted run leaves them untouched. Defaults to False, which merges this run into the existing records as a running average.
verbose Print error detail as each solution runs. Defaults to False.
silent Suppress this command's output; errors and the result line still show.

repeats

benchmark does not take a runs argument — it passes runs=None to the evaluator, which makes load_test_cases (core/test_cases.py) choose the repeat count per test-case category from the previously recorded timings::

runs = clamp(round(21 / slowest_prior_average), 1, 21)

where slowest_prior_average is the largest recorded average (seconds per run) among prior correct results for that category and the solutions being benchmarked. So each case is repeated ~21 times when it runs in well under a second and scales down toward a single run as it gets slower — keeping the per-category wall time bounded at roughly 21s — clamped to the 1..21 range. With no prior correct result recorded for a category the count is 1: the first benchmark establishes a one-run baseline, and later benchmarks use it to repeat the fast cases and average out noise. Passing disable_timeout overrides this and forces a single run.

Defined in solver.core.evaluate.benchmark.


Command: blog

List a blog article's declared tags and what each one maps to.

  • ⚑ needs reader or above.
  • ✎ asks for anything you leave out.

Reads the article's <!-- tags: [...] --> declaration and, for each slug, prints its facet and the problem/solution refs recorded on the central vocabulary's leg — the other half of the double-entry graph update-tags keeps in step. Fails when no article matches.

usage

blog
[name=<str>] (asked)

arguments

argument description
name The article to report on: a folder/leaf path under blogs/, or a bare leaf name when it is unambiguous. Offered as a menu when omitted; not strict, so a leaf name still works.

Defined in solver.core.tags.blog.


Command: blogs

List a problem's tags and the blog articles that cover them.

  • ⚑ needs reader or above.
  • ❏ uses/sets current problem.

Reads the problem's own tags.json — grouping the tags by facet (domain, technique per solution index, takeaway) — then names every article under blogs/ that declares any of them. Fails when the problem has no tags.json; update-tags reports which problems are missing one.

usage

blogs
[problem=<n>] (default current)

arguments

argument description
problem The problem whose tags to list.

Defined in solver.core.tags.blogs.


Command: check-commands

Report every command docstring that breaks the documented standard.

  • ⚑ needs admin.
  • » supports --silent to suppress output.

The standard is docs/developer-guide.md §3.8; the rules are listed in this module's docstring. Findings print as command | rule | detail rows, and the exit code is non-zero when there is at least one — so the command gates a chain and serves as the docstring lane of scripts/linters/check.sh packages.

Admin-floored on purpose, not out of caution. Registration is itself profile-filtered (packages/solver/solver/shell/command.py): a command above your floor is never registered, so registry.all() returns only what you may run. Checking from a lesser profile would pass by never looking at the commands it cannot see — the same reasoning that floors update-docs.

usage

check-commands
[name=<str>] (default '')
[silent=true|--silent]

arguments

argument description
name Check only the command (or alias) named, instead of the whole registry. Defaults to '', which checks every registered command.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.utils.doclint.check_commands.


Command: claude

Run Claude Code interactively in the repository root, returning its exit code.

  • ⚑ needs contributor or above.

Shorthand for ! claude …, with the same behaviour: Claude Code takes over the terminal, starts in the repo root (so it reads CLAUDE.md and the project skills), and its exit code is this command's. Every argument passes through to the claude CLI untouched — claude -c, claude --resume <id>, claude -p "…", claude mcp list — none is read by the shell. Unlike claude-solve, no problem or skill is implied. Needs the claude CLI on PATH.

usage

claude [options] [command] [prompt]

arguments

argument description
*args Claude Code's own options, subcommand and prompt, token by token — quoted before they reach the CLI. With none, an interactive session starts.

Defined in solver.ai.skill.claude.


Command: claude-api

Generate one of a problem's solution artifacts through the Claude API.

  • ⚑ needs contributor or above.
  • ❏ uses/sets current problem.
  • ✎ asks for anything you leave out.

Dispatches to the generator for target, prints the USD/EUR cost of the call, and fails if the generator reports failure.

usage

claude-api
[problem=<n>] (default current)
[target=c|py|doc|notes|tags|test-cases] (asked)
[force=true|--force]
[major=true|--major]
[model=claude-fable-5-1|claude-fable-5|claude-fable-5-2|claude-opus-5|claude-opus-4-8|claude-opus-4-7|claude-opus-4-6|claude-opus-4-5|claude-opus-5-5|claude-sonnet-4-6|claude-sonnet-4-5|claude-sonnet-5-5|claude-sonnet-5|claude-haiku-4-5|claude-haiku-5-5|none] (default None)

arguments

argument description
problem The problem to generate for.
target What to generate: 'c' or 'py' for code, 'doc' to refresh the in-source documentation, 'notes' for notes.html, 'tags' for tags.json, 'test-cases' for test cases. Offered as a menu when omitted.
force Generate even when the target already exists, overwriting it. Defaults to False.
major Regenerate after a major change — a new template or changed instructions — rather than an incremental one. Defaults to False.
model The model to generate with. Defaults to None, which picks Opus for code, docs and notes, and Sonnet for tags and test cases.

Defined in solver.ai.api.claude_api.


Command: claude-batch

Bulk-tag a wave of problems in one Message Batches job, at half price.

  • ⚑ needs maintainer or above.

Solved problems get the full prompt (domain + per-index techniques + takeaways); unsolved ones get the domain-only prompt. Run update-tags after each wave to promote the proposed new-tags before submitting the next, so later waves are prompted with the settled vocabulary.

Note: the costs command counts batched tokens at standard list price, so its total overstates a batch wave by roughly 2x; this command prints the true discounted cost.

usage

claude-batch
[action=run|submit|collect|list] (default run)
[target=solved|unsolved|untagged|all] (default untagged)
[limit=<int>] (default 250)
[start=<int>] (default 0)
[batch_id=<str>] (default '')
[problems_list=<str>] (default '')
[model=claude-fable-5-1|claude-fable-5|claude-fable-5-2|claude-opus-5|claude-opus-4-8|claude-opus-4-7|claude-opus-4-6|claude-opus-4-5|claude-opus-5-5|claude-sonnet-4-6|claude-sonnet-4-5|claude-sonnet-5-5|claude-sonnet-5|claude-haiku-4-5|claude-haiku-5-5] (default claude-sonnet-5)

arguments

argument description
action run submits, waits and collects; submit returns as soon as the job is queued; collect writes the results of an already-submitted job; list shows waves that were submitted but not yet collected.
target Which problems to cover — untagged (no tags.json yet, the resumable default), solved, unsolved, or all.
limit Maximum problems in this wave. Keep it to a few hundred so that new-tags proposals get reconciled often enough to avoid duplicate slugs.
start Skip problems numbered below this. Paces a re-tag through the stack in waves, where untagged cannot help because every problem already has a file.
batch_id The job to collect; required for collect.
problems_list Comma-separated problem numbers to run instead of a target sweep — the targeted re-run after a vocabulary change (23,39,146). Overrides target.
model The model to run the wave on.

Defined in solver.ai.batch.claude_batch.


Command: claude-blog

Write (or flesh out) a blog article via the claude-euler-blogger skill.

  • ⚑ needs maintainer or above.
  • ✎ asks for anything you leave out.

blog names what to write about: a tag's <facet>/<slug> path (e.g. technique/sieve-of-eratosthenes), a bare tag slug, or a curated blog path (number-theory/primes). Tab-completion offers every blog in the article index (blogs/articles.json), unwritten and most-referenced first. Launches Claude Code headless to research the covering problems and write the article under blogs/, then streams a live Markdown summary. Needs the claude CLI, signed in (claude-login).

A blog whose article the index reports as final is left alone — the skill marks a page final when it is done writing it, and rewriting one is an explicit --force.

usage

claude-blog
[blog=<str>] (asked)
[additional_prompt=<str>] (asked)
[force=true|--force]

arguments

argument description
blog The tag or blog to write about. Offered as a menu of the article index when omitted; completion offers the same set.
additional_prompt Extra free-text guidance for the writer. Asked for in an interactive shell when omitted — the web's Write / Rewrite actions type a bare claude-blog <path>, so a maintainer would otherwise never get to pass an angle. Enter skips it. Defaults to empty.
force Rewrite the article even when it is already final. Defaults to False.

Defined in solver.ai.skill.claude_blog.


Command: claude-login

Sign Claude Code in, then report whether it is, returning 0 only when signed in.

  • ⚑ needs contributor or above.

Shorthand for ! claude /login followed by claude auth status: Claude Code takes over the terminal for its login (and, on a first run, its onboarding), then reports its own sign-in state. The report runs whether or not the login was cancelled, and its status is this command's — the question it answers is "is Claude Code signed in now?". Needs the claude CLI on PATH (tool-setup claude installs it).

usage

claude-login

Defined in solver.ai.skill.claude_login.


Command: claude-solve

Run Claude Code over a problem's solution files, via a skill.

  • ⚑ needs contributor or above.
  • ❏ uses/sets current problem.
  • ✎ asks for anything you leave out.

Launches Claude Code headless against the given problem's solution directory, runs the requested action, and streams a live-updating Markdown summary back into the shell, ending with a footer of turns / duration / cost. Heavier and slower than claude-api — it actually runs solver commands, edits files, evaluates, and iterates. Needs the claude CLI on PATH, signed in (claude-login).

usage

claude-solve
[problem=<n>] (default current)
[action=solve|review] (asked)
[additional_prompt=<str>] (default '')

arguments

argument description
problem The problem to work on.
action What to do — 'solve' writes and verifies a Python solution, translates it to C, then documents and summarises it; 'review' audits an existing solution for C↔Python parity, in-source documentation and notes.html. Offered as a menu when omitted.
additional_prompt Extra free-text instructions appended to the skill invocation. Defaults to empty.

Defined in solver.ai.skill.claude_solve.


Command: clear (cls)

Clear the terminal screen and scrollback, then succeed.

  • ⚑ needs reader or above.

A convenience wrapper over the console's clear; equivalent to the shell clear. Takes no arguments — any given are ignored. Aliased as cls.

usage

clear

Defined in solver.shell.builtins._clear.


Command: compile-c (compile)

Compile every C solution for the problem into a runnable binary.

  • ⚑ needs contributor or above.
  • ❏ uses/sets current problem.
  • » supports --silent to suppress output.

Builds each .c file in problem.solution_dir/ (linking the runner harness) so it can be evaluated and benchmarked; reports per-file success or the compiler error. eval and benchmark invoke this for you, so you rarely call it directly.

usage

compile-c
[problem=<n>] (default current)
[clean=false|--no-clean]
[silent=true|--silent]

arguments

argument description
problem The problem whose C sources to compile.
clean When False, reuse up-to-date build output from previous compilations. Defaults to True.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.evaluate.compile_c.


Command: costs

Print what this session's AI tokens have cost, per model.

  • ⚑ needs contributor or above.

Totals the charges across every model in consumed_tokens using its published USD price per million tokens (cache writes at 1.25x and cache reads at 0.10x the input rate), then converts to EUR. Prints "No charges so far." when nothing has been consumed; either way it succeeds.

usage

costs
[ecb_usd_rate=<float>] (default 1.1214)

arguments

argument description
ecb_usd_rate The conversion rate to price the total in EUR (1 € = N $). Defaults to the configured ecb_usd_rate.

Defined in solver.ai.models.costs.


Command: create-blog

Seed a curated cross-cutting blog page.

  • ⚑ needs maintainer or above.
  • ✎ asks for anything you leave out.

A curated blog (e.g. number-theory/primes) gathers several tags under one idea, unlike a tag's own auto-generated page.

path is the folder/leaf the page lives at under blogs/. Give the tag slugs as arguments to create non-interactively; give none and, in an interactive shell, pick them with a search-and-select prompt. Every slug must exist in the vocabulary. The page is seeded from the same template a tag page uses (its title is the leaf, title-cased — edit it when you write the page) and reconciled straight away, so it appears in the draft index and its Problems section is filled. Write it with claude-blog <path>.

usage

create-blog <path>
[tags=<str>] (asked)

arguments

argument description
path The folder/leaf location under blogs/ (lowercase words and hyphens).
*tags The tag slugs the page covers. Omit them and, in an interactive shell, pick them by search-and-select over the vocabulary.

Defined in solver.core.tags.create_blog.


Command: echo

Print the given text to the console, then succeed.

  • ⚑ needs reader or above.

Handy in command blocks to annotate progress or to surface a variable, since {...} references are substituted before the command runs — e.g. echo {problem.title}, or n = len({solved}); echo solved {n} problems.

usage

echo <text>

arguments

argument description
*args The words to print. They are joined with single spaces and printed literally, with no rich markup interpretation.

Defined in solver.shell.builtins._echo.


Command: edit (ed)

Open a solution file in the web code editor.

  • ⚑ needs contributor or above.
  • ❏ uses/sets current problem.
  • ✎ asks for anything you leave out.
  • » supports --silent to suppress output.

The counterpart to show (which opens the rendered problem): problem defaults to the current problem, and filename completes to the files ls lists. The file must already exist — run new to create a solution first. Opens the file's editor (<base_url>/edit/solutions/NNNN/<file>) in the named browser tab "solver-edit" (via browser open-in-tab), and errors early if the browser command is unavailable; in the web shell it opens in the viewer panel instead.

usage

edit
[problem=<n>] (default current)
[filename=<str>] (asked)
[silent=true|--silent]

arguments

argument description
problem The problem owning the file.
filename The solution-directory file to edit, as ls lists it. Offered as a menu when omitted. It must already exist.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.viewer.edit.


Command: evaluate (eval)

Evaluate a problem's solutions against its test cases.

  • ⚑ needs contributor or above.
  • ❏ uses/sets current problem.

Compiles what needs compiling, then runs every solution the problem has — each language, each solution index — against every test case in the chosen categories, printing a verdict line per run: correct, incorrect, error, overflow, timeout or unknown (a case with no recorded answer). Fails unless every run was correct, so it gates a chain: eval 42 && benchmark.

This is the correctness check and it records nothing. benchmark is the measuring counterpart — same runs, but written to results.json and repeated an adaptive number of times.

usage

evaluate
[problem=<n>] (default current)
[all|dev|main|extra ...]
[clean=false|--no-clean]
[timeout=<float>|none] (default None)
[disable_timeout=true|--disable-timeout]
[lang=*|py|c] (default *)
[runs=<int>] (default 1)
[show=true|--show]
[solution_index=<int>|none] (default None)
[verbose=true|--verbose]

arguments

argument description
problem The problem whose solutions to evaluate.
*categories Test-case categories to include: 'dev', 'main', 'extra', or 'all' (which expands to all three). Defaults to 'dev' and 'main'.
clean When False, reuse up-to-date build output from previous compilations. Defaults to True.
timeout Timeout in seconds for a solution's execution. None uses the configured default. Defaults to None.
disable_timeout Run without a timeout, and only once per test case. Defaults to False.
lang Which language to evaluate: '', 'py' or 'c'. Defaults to ''.
runs Number of times to run each solution per test case, useful for timing. Defaults to 1.
show Append '--show' to the arguments passed to each solution, so it prints its own diagnostics. Defaults to False.
solution_index Evaluate only this solution index. None evaluates every solution. Defaults to None.
verbose Print error detail as each solution runs. Defaults to False.

Defined in solver.core.evaluate.evaluate.


Command: gh-merge (merge)

List the open pull requests, or rebase-merge one onto master.

  • ⚑ needs maintainer or above.
  • ✎ asks for anything you leave out.
  • » supports --silent to suppress output.

list (the default) shows what is waiting: number, title, branch. merge lands one of them, named by number and offered as a menu when you leave it out. Merging is how a collaborator's user/<slug> branch reaches master, each of its commits replayed onto the tip; their next git-sync then rebases those already-applied commits away and prunes the merged branch. It also dismisses the notice git-push filed for that branch — the message asked for this review, and it is done.

Every file in the pull request must sit inside the allowlist: solutions/, docs/, blogs/, and the specific files the update target names — which is exactly what git-commit can stage. One file outside it refuses the whole request. That is the gate, and the only one: a request may carry as many commits, across as many targets, as the work needed. What makes this a maintainer's command rather than an admin's is that everything in it came from a commit verb they know the shape of; a branch that also edits the framework, the scripts, or the keys is asking for something else entirely, and belongs on GitHub in front of an admin who has read it.

Aliased as merge.

usage

gh-merge
[action=list|merge] (default list)
[pr_number=<int>] (asked)
[silent=true|--silent]

arguments

argument description
action 'list' shows the open queue; 'merge' lands one request. Defaults to 'list'.
pr_number Which pull request to merge, by number. Offered as a menu of the open requests when omitted, so the number never has to be typed out; only ever required for merge.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.git.gh_merge.


Command: git-audit (audit)

Audit what git actually stores, across the whole tracked tree.

  • ⚑ needs contributor or above.
  • » supports --silent to suppress output.

Two checks, each reading every tracked blob straight from the object store (so no smudge filter runs): every file under solutions/private is stored as ciphertext, and no file anywhere is a compiled binary. Both run even when the first fails; a non-zero exit means one of them found something.

This is the periodic full sweep, and it takes ~25s. The git hooks run the same two checks scoped to the blobs at hand — git-hooks (pre-commit) audits what you staged, pre-push audits what the push would add — so committing and pushing stay fast. The cost of that scoping is that neither hook re-examines history already on origin; this is the command that does.

Aliased as audit.

usage

git-audit
[details=true|--details]
[silent=true|--silent]

arguments

argument description
details List every file audited. Defaults to False, which reports counts only; offenders are listed by path either way.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.git.git_audit.


Command: git-commit (commit)

Stage and commit the named targets — the one commit verb.

  • ⚑ needs contributor or above.
  • ❏ uses/sets current problem.
  • ✎ asks for anything you leave out.
  • » supports --silent to suppress output.

The routine "save my progress" step. With no target it commits solution: everything under this problem's directory, plus solutions/problems.json (the progress file mark rewrites), and nothing else. Naming targets widens that to whichever bodies of work you mean, and they compose — git-commit docs update lands a whole update-docs run as one commit, git-commit blogs lands both legs of the tag graph together. A target with nothing to stage contributes nothing rather than failing, so composing is cheap.

--amend folds the same paths into HEAD with its message untouched — the "I forgot something" step, so a checkpoint absorbs the fix instead of growing a "fix typo" commit behind it. It is refused once HEAD is on origin (rewriting a pushed commit needs a force-push, so committing again is the honest step there), and is a no-op, not a failure, when those paths are clean. A message and --amend are mutually exclusive: exactly one of them says what the commit is called.

Aliased as commit.

usage

git-commit
[problem=<n>] (default current)
[solution|solutions|docs|blogs|update ...]
[message=<str>] (asked)
[amend=true|--amend]
[reset=true|--reset]
[silent=true|--silent]

arguments

argument description
problem The problem whose solution directory the solution target stages. Ignored by every other target.
*targets What to stage — 'solution' (this problem plus the progress file), 'solutions' (the whole solution tree), 'docs' (the guides), 'blogs' (the articles and every problem's tag leg) or 'update' (what the update-* verbs write outside docs/ — the README, the module registry, the settings file, the model block, the Claude guidance). Defaults to 'solution'.
message The commit message. Required unless amend is set, and asked for at the prompt when it is left out.
amend Fold the staged changes into HEAD instead of committing, keeping its message. Defaults to False. Refused with a message, with reset, or once HEAD is pushed.
reset Soft-reset to origin/master first, so the new commit squashes all local commits into a single checkpoint (the working tree is untouched). Defaults to False. git-reset is the same move without the re-commit.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.git.git_commit.


Command: git-filter (filter)

Report or wire the transparent encryption filter for solutions/private.

  • ⚑ needs reader or above.

status shows the filter wiring and whether this session can unwrap the master key. install verifies master-key access first (refusing cleanly without it — nothing is wired), registers the clean/smudge filter in this clone's git config, and re-checks out solutions/private so existing ciphertext decrypts in place. The explicit form of what git-sync runs automatically after a pull that delivers key access — use it when access arrived some other way, e.g. right after key-reconstruct from shares.

Aliased as filter.

usage

git-filter
[action=status|install] (default status)

arguments

argument description
action 'status' reports whether the filter is configured; 'install' configures it. Defaults to 'status'.

Defined in solver.core.git.git_filter.


Command: git-hooks (hooks)

Run the git pre-commit and (simulated) pre-push checks on demand.

  • ⚑ needs contributor or above.
  • » supports --silent to suppress output.

Runs the same checks the git hooks run — the pre-commit hook (whitespace, staged artifacts, shellcheck, flake8, mypy) and a simulation of the pre-push hook — so you can verify your changes will pass before committing or pushing. Reports the combined pass/fail in the exit code.

Aliased as hooks.

usage

git-hooks
[silent=true|--silent]

arguments

argument description
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.git.git_hooks.


Command: git-identity (identity)

Configure your git identity and push credential from your GitHub login.

  • ⚑ needs contributor or above.

The one-time setup before git-push: runs gh auth login when you are not yet signed in (interactive device flow — works in the web shell), makes gh the git credential helper (gh auth setup-git), and sets this clone's user.name / user.email from your GitHub profile, so your commits are authored and pushed as you, never as a service identity.

Aliased as identity.

usage

git-identity

Defined in solver.core.git.git_identity.


Command: git-publish (publish)

Publish changed files for named targets to the remote repository.

  • ⚑ needs maintainer or above.
  • » supports --silent to suppress output.

Fails if any target is not one of the accepted scopes.

usage

git-publish
[scripts|solutions|packages ...]
[dry_run=true|--dry-run]
[silent=true|--silent]

arguments

argument description
*targets Scopes of files to publish — one or more of 'scripts', 'solutions' or 'packages' (the code). There is no keys scope: key material is not distributed by git any more. A holder's enc-key file is machine-local, and issuing one is user-authorize sending it through the message spool. Defaults to 'solutions'.
dry_run Print the push and pull-request commands instead of running them. Defaults to False.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.git.git_publish.


Command: git-push (push)

Push the current branch to origin as yourself, then open its pull request.

  • ⚑ needs contributor or above.
  • » supports --silent to suppress output.

In a per-user clone the current branch is user/<slug>, pushed with your own GitHub identity — git-identity is the one-time setup. Landing work on master is a maintainer's gh-merge, never a direct push: pushing master requires the admin floor, and force-pushing it is always refused.

The PR is the second half of the push: an unreviewed branch on origin is not work anyone has been asked for. It is skipped on master (nothing to merge into itself) and on a branch level with origin/master (nothing to review), and a branch that already has one open keeps it. Opening one also messages the maintainers that it is waiting, so the review does not depend on somebody thinking to look; their verb is gh-merge merge.

usage

git-push
[force=true|--force]
[pr=false|--no-pr]
[silent=true|--silent]

arguments

argument description
force Push with --force-with-lease — needed after git-sync rebased your branch onto a moved origin/master. Refused on master. Defaults to False.
pr Open a pull request onto master after a successful push. Defaults to True; --no-pr pushes and stops there.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.git.git_push.


Command: git-reset (reset)

Soft-reset your branch to origin/master — un-commit, keep every change.

  • ⚑ needs reader or above.
  • » supports --silent to suppress output.

The undo git-commit --reset never lets you stop at: this runs git reset --soft origin/master, moving your branch tip back to origin/master while leaving the working tree untouched, so every local commit's changes survive as staged edits. From there re-commit differently (git-commit), restage selectively, or leave it — whereas git-commit --reset squashes straight into one fresh commit and gives you no such pause.

Makes no commit, so it runs no hooks and is a clean no-op — exit 0 — when your branch is already level with origin/master. Undone commits are not lost: they stay reachable through the reflog until git eventually prunes them.

--hard instead makes the clone match origin/master exactly — the way out of a clone left mid-merge (unmerged paths, a stash pop or rebase that stopped part-way), which git-sync refuses to sync over. It fetches first, lists what it is about to discard — local commits, every uncommitted change, and every stash entry — and asks you to type discard to confirm. Untracked files are left alone.

The stash goes too because a wedged sync is what fills it: each failed git-sync stashes the half-applied tree, and popping one later re-creates the wedge. Without this a reader (who has no !) could not clear it at all.

Aliased as reset.

usage

git-reset
[hard=true|--hard]
[silent=true|--silent]

arguments

argument description
hard Discard local commits, uncommitted changes and stash entries, resetting the working tree to origin/master, after a typed confirmation. Defaults to False.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.git.git_reset.


Command: git-status (status)

Display the sync state between the local branch and origin/master.

  • ⚑ needs reader or above.

Reports how far ahead and behind your branch is, and what is uncommitted in the working tree — the read before deciding between git-push, git-sync and git-commit. It is as fresh as the last fetch; git-sync is what refreshes it. Aliased as status.

usage

git-status
[details=true|--details]

arguments

argument description
details List every differing file and uncommitted change. Defaults to False, which shows file counts only.

Defined in solver.core.git.git_status.


Command: git-sync (sync)

Bring the local repository in sync with origin/master.

  • ⚑ needs reader or above.

On a per-user clone (branch user/<slug>) this is the pull flow: fetch origin/master and merge/rebase it into your branch — bringing in merged work merged work. Key material does not travel this way -- it is issued by message (user-authorize / msg act), and that is what wires the filter.

Stale remote-tracking refs are pruned as part of the fetch, so a branch deleted when its pull request merged stops shadowing the branch you push next.

usage

git-sync
[dry_run=true|--dry-run]

arguments

argument description
dry_run Print the sync commands instead of running them. Defaults to False.

Defined in solver.core.git.git_sync.


Command: host-authorize

Mail the master key, sealed to one machine's public key — the off-host grant.

  • ⚑ needs admin.
  • ✎ asks for anything you leave out.

key-split needs both halves to be reachable: the sealed message, and the half sitting on this host. A machine that is not on this host has no second half, so there is nothing to complete — and the spool cannot reach it either. This is that case, and it trades the split's third factor for a channel that actually arrives: the whole master key, sealed to the machine's public key so it is inert to the mail provider and to every mailbox it passes through, delivered by e-mail with a marked block to copy.

The far end runs host-unlock and pastes the block. Nothing is recorded here and nothing is registered: an off-host machine has no account, no slug and no instance — which is why this asks for a public key rather than an identity.

Mail is submitted through the loopback relay from this terminal, which the egress firewall permits for the operator's uid and bars for every per-user one. When the relay cannot be reached the block is printed instead, so the grant is never lost to a mail problem — send it yourself, by any channel you trust.

usage

host-authorize
[public_key=<str>] (asked)
[email=<str>] (asked)

arguments

argument description
public_key The 64-hex X25519 public key of the machine being authorized — what user prints there.
email Where to send it. Their address, or your own if you intend to forward it by another route.

Defined in euler_webshell.shell.keys.host_authorize — on the web host only (the web shell and solver-host).


Command: host-unlock

Take the mailed block from host-authorize and unlock this machine.

  • ⚑ needs admin.
  • ✎ asks for anything you leave out.

The receiving half of the off-host grant, and the same proof as every other way in: the payload must unwrap with this machine's private key and its verify must decrypt to the known text. Only then does it replace what is here — the file it overwrites may be the only thing between this machine and the whole private tree, so a bad or mistargeted block has to fail before the write, not be discovered after it.

Paste everything between the markers; the surrounding prose is ignored, and so is whatever the mail client did to the line breaks.

usage

host-unlock
[payload=<str>] (asked)

arguments

argument description
payload The block from the mail. Asked for over several lines when it is not given — end with a blank line.

Defined in solver.crypto.keys.host_unlock.


Command: key-export

Print the master key sealed to a machine's public key, to carry there yourself.

  • ⚑ needs admin.
  • ✎ asks for anything you leave out.

The plain way to issue the key, needing no spool and no mail: the whole master key, sealed to the machine's X25519 public key so it is inert to anyone without that machine's private key, printed between markers. Carry it by any channel you trust; the far end runs host-unlock and pastes it. The web host's host-authorize mails the same block.

usage

key-export
[public_key=<str>] (asked)

arguments

argument description
public_key The 64-hex X25519 public key of the machine — what user prints there.

Defined in solver.crypto.keys.key_export.


Command: key-reconstruct

Unwrap the half you were sent, complete it from the repository, store the key.

  • ⚑ needs reader or above.
  • ✎ asks for anything you leave out.

What msg act does with a key-split message, and what to run by hand when the share reached you some other way. The half sent to you is sealed to your public key, so only this machine's private key opens it; the other half is the one this machine already holds (/etc/euler/share.json on the host). The reconstructed key is proved before anything is written, and then stored wrapped to your public key. Needs a private key already in place: run user first.

usage

key-reconstruct
[share=<str>] (asked)

arguments

argument description
share The share from your message — the wrapped blob as sent, or a bare 262-hex share handed to you out of band. Asked for when it is not given.

Defined in solver.crypto.keys.key_reconstruct.


Command: key-rekey (rekey)

Rotate the master key and re-encrypt every private file under the new one.

  • ⚑ needs admin.

Revocation lives here, and it is the only thing that revokes. Dropping somebody's access means rotating the key they hold and issuing the new one to everyone else.

Every other holder loses access at the rotation until the new key reaches them. Where the shell has a roster and a message spool (the web shell, solver-host), this also re-issues it to every registered public key, and names anyone it cannot reach; elsewhere, carry it to each holder with key-export.

Because the git filter is deterministic, every committed blob depends on the master key, so a rotation re-encrypts the tracked private files via git add --renormalize.

usage

key-rekey

Defined in solver.crypto.keys.key_rekey.


Command: key-split

Send someone half the master key, sealed to their public key.

  • ⚑ needs maintainer or above.
  • ✎ asks for anything you leave out.

Two halves of a 2-of-2 split, and neither is worth anything alone. One sits on this host (/etc/euler/share.json, readable by every uid there and written only by the operator); the other is minted per recipient, sealed to that recipient's X25519 public key — read from the roster — and sent through the message spool, which they take with msg act: that runs key-reconstruct, unwraps their half, puts the two together, and writes their enc-key file.

So a delivery has three independent parts and an attacker needs all of them: the message, the private key that opens it, and access to the host. That is the difference from an issued key, which is inert without the private key and nothing more — a stolen ~/.euler plus a copy of the spool is enough for one and not for the other.

The first run is the host's: with no local half yet (or one that predates the current master key) this writes it and stops, so nobody is sent a half they could not complete. Run it again to send. Off this host there is no shared half at all — use host-authorize.

usage

key-split
[identity=<str>] (asked)
[public_key=<str>] (default '')

arguments

argument description
identity Who to send the other half to. Not asked — and not used — on the run that writes the repository's share.
public_key The recipient's 64-hex public key, for somebody the roster has no key for yet — a first grant, before anyone has been authorised. Defaults to '', which reads it from the roster.

Defined in euler_webshell.shell.keys.key_split — on the web host only (the web shell and solver-host).


Command: lint

Lint the problem's solution files, optionally auto-fixing them.

  • ⚑ needs contributor or above.
  • ❏ uses/sets current problem.
  • » supports --silent to suppress output.

Checks the current problem's solution files for style and quality issues (flake8, plus the configured checks). Reports any findings and reflects them in the exit code.

usage

lint
[problem=<n>] (default current)
[auto_fix=true|--auto-fix]
[silent=true|--silent]

arguments

argument description
problem The problem whose solution files to lint.
auto_fix Fix what can be fixed in place — autoflake (unused imports and variables), autopep8 (style) and isort (import order) — then re-check. Defaults to False, which only reports.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.utils.linter.lint.


Command: logout

Sign out of the web session, after a short countdown the notice can skip.

  • ⚑ needs reader or above.

The page signs out the way the header's Logout does: the session and its remember-me token end, the vault locks and this shell closes. Web shell only: a terminal session ends with exit.

usage

logout

Defined in euler_webshell.shell.logout.logout — on the web host only (the web shell and solver-host).


Command: logout-all

Sign every web user out, after a countdown that running work can extend.

  • ⚑ needs admin.

Everyone with a terminal open sees the reason and a countdown; the notice's button signs them out at once, killing any work they have running. Work still running holds the shell past the countdown, up to a limit, then is killed. The command waits for all of that, then revokes every session and remember-me token. redeploy-web, upgrade-web and remove-web run it first. Runs under sudo, so it is the operator terminal's.

usage

logout-all
[reason=<str>] (default the server is shutting down)
[extend=<int>] (default 0)
[now=true|--now]

arguments

argument description
reason What the notice tells users.
extend Add this many seconds to a notice already running, then return.
now Close every shell at once, running work or not, with no notice.

Defined in euler_webshell.users.logout_all — on the web host only (the web shell and solver-host).


Command: ls

List the files in a problem's solution directory.

  • ⚑ needs reader or above.
  • ❏ uses/sets current problem.
  • » supports --silent to suppress output.

Walks the directory recursively and prints each file's canonical path, size, modified time and MIME type, sorted by path; a solution file (pNNNN_sK.*) is flagged with a trailing *. Files with no extension, and anything under __pycache__, are skipped. Fails when the directory holds nothing to list.

usage

ls
[problem=<n>] (default current)
[silent=true|--silent]

arguments

argument description
problem The problem whose solution directory to list.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.list.ls.


Command: mark (mark-solved)

Mark the current problem as solved — once its results confirm it.

  • ⚑ needs contributor or above.
  • ❏ uses/sets current problem.
  • » supports --silent to suppress output.

Records the current problem as solved (with today's date) in problems.json, the same state summary maintains, so {solved}, progress, and solved reflect it without re-importing the progress page.

It only proceeds after checking the recorded results: there must be a selected problem, its test_cases.json must have a main case with an answer, and results.json must contain a correct verdict for that main case. Run benchmark (which records results) first; a problem already marked solved is left unchanged.

Aliased as mark-solved.

usage

mark
[problem=<n>] (default current)
[silent=true|--silent]

arguments

argument description
problem The problem to mark solved.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.summary.mark.


Command: msg (messages)

Read and send messages: what is waiting for you, and what it asks you to do.

  • ⚑ needs reader or above.
  • ✎ asks for anything you leave out.

Every message has staff (maintainer+) at one end: you can ask them something, they can answer, and they can send notices. There is deliberately no user-to-user messaging. Delivery is asynchronous — the spool holds the message until you read it.

Five verbs, and each is one thing a person does with a message. act is the one worth knowing: a message knows what it is for, so acting on one takes the key it carries, grants the key it asks for, merges the pull request it announces, or walks you through registering the answers summary found unregistered — and on anything else it simply reads it. The header's message chip labels its rows from the same rule.

Typed bare, it walks you through the rest: pick a verb, then whatever that verb needs — a message from your own list, or a subject and a body. Every answer can be given on the command line instead, and a non-interactive shell asks nothing.

usage

msg
[action=list|read|send|dismiss|act] (asked)
[thread=<str>] (asked)
[subject=<str>] (asked)
[body=<str>] (asked)
[to=<str>] (asked)

arguments

argument description
action What to do — list everything waiting for you, newest first; read one message and mark it read; send a message; dismiss one you are done with; act on one, doing what it asks. Defaults to list.
thread The message to act on, for read / dismiss / act. Offered as a menu of your own messages, so the id never has to be typed out.
subject The subject line, for send.
body The message text, for send.
to Who a send goes to: staff (the default, and the only audience below the maintainer floor), everyone, or one or more identities, comma-separated. Staff are offered the known accounts as a menu where the roster can be read.

Defined in euler_webshell.msg.commands.msg — on the web host only (the web shell and solver-host).


Command: new

Generate new solution and/or test-case files for the problem.

  • ⚑ needs contributor or above.
  • ❏ uses/sets current problem.
  • » supports --silent to suppress output.

Solution files are named from the problem number and the next free solution index (e.g. "p0001_s0.py", "p0001_s1.py") and are created from the boilerplate template with the problem information substituted; Python files are made executable (mode 0o755).

With neither py nor c given (and tc False), both a Python and a C file are created.

usage

new
[problem=<n>] (default current)
[py=true|--py]
[c=true|--c]
[tc=true|--tc]
[silent=true|--silent]

arguments

argument description
problem The problem to create files for.
py Create a Python solution file. Defaults to False.
c Create a C solution file — one per existing Python solution that lacks a matching ".c". Defaults to False.
tc Create an empty test-cases file instead of solution files, unless one already exists. Defaults to False.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.new.new.


Command: pause

Wait for the user to press Enter before the block carries on.

  • ⚑ needs reader or above.

A beat in a command block — after a show, or between the steps of a walkthrough. With nobody to wait for (a pipe, < /dev/null, --silent) it is a no-op rather than an error, so a block that pauses is still scriptable. Leaving the prompt with Ctrl-C or Ctrl-D fails the command, so pause "ready?" || break stops a loop.

usage

pause
[prompt=<str>] (default paused)

arguments

argument description
prompt The line shown above the prompt — what the user is confirming, e.g. pause "registered the answer?". Defaults to paused.

Defined in solver.utils.shell_utils.pause.


Command: pip-upgrade (upgrade)

Upgrade packages in the current venv for the given dependency groups.

  • ⚑ needs admin.

Groups are read from the packages' pyproject.toml files: 'core' for euler-solver's project.dependencies, 'ai', 'dev', 'solutions', 'show' for its optional-dependencies, 'web' for euler-site's and euler-webshell's dependencies, 'all' to upgrade everything ('web' only when the web tier is installed in this venv). Defaults to 'all'.

usage

pip-upgrade
[all|ai|core|dev|solutions|show|web ...]

arguments

argument description
*groups One or more dependency group names, or 'all'.

Defined in solver.utils.scripts.pip_upgrade.


Command: problems

Print a list of problems and their count.

  • ⚑ needs reader or above.

The set comes from problems.json, the state summary imports from your Project Euler progress page and mark updates as you solve — so "solved" means recorded there, not merely present on disk.

usage

problems
[which=all|solved|unsolved] (default all)

arguments

argument description
which Which set to list — 'all' every known problem, 'solved' those with a recorded answer, 'unsolved' those without. Mirrors the {problems} / {solved} / {unsolved} shell variables. Defaults to 'all'.

Defined in solver.utils.misc.problems.


Command: progress

Print overall progress through the Euler problems.

  • ⚑ needs reader or above.

Shows a bar of solved vs. unsolved problems, the solved count and percentage of the total known problems, and the next problem to solve (the lowest-numbered unsolved one). Reads the state maintained by summary; run summary first if your progress looks out of date.

usage

progress

Defined in solver.core.summary.progress.


Command: random

Return a random whole number from min to max, both included, as the exit code.

  • ⚑ needs reader or above.

The number is the exit code, which is what an assignment stores — so wait = random 3600 1800 sets wait to a number from 1800 to 3600, ready for sleep {wait}. That also means a result above 0 reads as failure to && / ||: assign it, don't chain on it. (solver "random" exits the process with it, which the operating system truncates to 0–255.)

usage

random
[max=<int>] (default 100)
[min=<int>] (default 1)

arguments

argument description
max The largest number it may return. Defaults to 100.
min The smallest number it may return. Defaults to 1. Must not exceed max.

Defined in solver.utils.shell_utils.random.


Command: results

List the recorded results for a problem.

  • ⚑ needs reader or above.
  • ❏ uses/sets current problem.

Reads the results.json that benchmark (and eval --record) writes in the problem's solution directory, and prints one line per recorded run — verdict, average time, run count, solution and arguments. Fails when there is no results file to read.

usage

results
[problem=<n>] (default current)
[all|dev|main|extra ...]

arguments

argument description
problem The problem whose results to list.
*categories Test-case categories to include: 'dev', 'main', 'extra', or 'all' (which expands to all three). Defaults to all three.

Defined in solver.core.results.results.


Command: search (find)

Search the solution stack for a case-insensitive regular expression.

  • ⚑ needs reader or above.

For every problem in scope, each matching stack file is read (decrypted as needed) and scanned line by line; every matching line is printed as '/: ' with the matched substring highlighted. A blank line separates the matches of one problem from the next.

usage

search <query>
[*|py|c|html|json ...]
[scope=problems|solved] (default solved)

arguments

argument description
query Regular expression to search for, matched case-insensitively against each line (re.search, so it need not match the whole line).
*files File extensions to include, without the leading dot. Defaults to 'py' and 'html'; '*' expands to the full set 'py c html json'.
scope Which problems to search: 'solved' restricts to solved problems, 'problems' covers every known problem. Defaults to 'solved'.

Defined in solver.utils.search.search.


Command: show (open, view)

Open a problem's page, or any site page, in a browser or the web viewer panel.

  • ⚑ needs reader or above.
  • ❏ uses/sets current problem.
  • » supports --silent to suppress output.

When problem is omitted, opens the current problem. Opens the problem's page (<base_url>/solutions/NNNN/) in the named browser tab "solver-doc" (via browser open-in-tab). Every show reuses that one tab: the same problem is focused and refreshed, a different problem navigates the tab in place, and the tab is recreated if it has been closed. Prints an error and returns early if the "browser" command is not available. In the web shell, which has no local browser to drive, the page opens in the viewer panel instead.

When path is given, show opens that site page instead of the problem's — show blogs/technique/concatenation. The page ignores problem, but naming one still makes it the current problem: show 121 blogs/… selects 121.

usage

show
[problem=<n>] (default current)
[path=<str>|none] (default None)
[silent=true|--silent]

arguments

argument description
problem The problem to open (and select as the current problem).
path A relative site path to open instead, e.g. blogs/technique/concatenation. Its first segment must be one of solutions, blogs, docs, about, edit, account or shell. Defaults to None, which opens the problem's page.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.viewer.show.


Command: sleep

Wait the given number of seconds before the block carries on.

  • ⚑ needs reader or above.

Blocking, like the shell's sleep: the block resumes when the time is up. Says when that will be, so a long wait is not mistaken for a hang. Ctrl-C ends it early and fails the command, so sleep {wait} || break stops a loop.

usage

sleep <seconds>

arguments

argument description
seconds How long to wait, in whole seconds; 0 returns at once. Must not be negative.

Defined in solver.utils.shell_utils.sleep.


Command: summary

Refresh the solved/unsolved state from your Project Euler progress page.

  • ⚑ needs maintainer or above.
  • » supports --silent to suppress output.

Parses solutions/.progress.html (the saved Page Source of your authenticated https://projecteuler.net/progress page) and updates problems.json with which problems are solved and their metadata. This is how the shell learns your real progress, driving {solved} / {unsolved}, progress, and solved.

It is also where new problems arrive: each problem the page lists that has no solution directory yet gets its statement and resources downloaded. A failed download fails the command once the rest are done; re-running retries it.

What it wrote is committed — problems.json and each new problem's statement, __init__.py and resources, and nothing beside them — so new problems reach other clones by git-sync instead of every clone fetching its own copy.

The import only ever adds solved problems: a problem mark recorded as solved keeps that record, and its date, even when the page does not show it as solved — which is the normal state of a problem solved here but whose answer has not been registered on projecteuler.net yet. Each such disagreement is reported, and staff are sent a message naming the problems.

Returns an error (with instructions) if .progress.html is missing: visit the progress page, copy its Page Source into that file, and retry.

usage

summary
[silent=true|--silent]

arguments

argument description
silent Suppress this command's output; errors and the result line still show.

Defined in solver.core.summary.summary.


Command: sys-setup (install)

Install or uninstall a system resource.

  • ⚑ needs admin.
  • ✎ asks for anything you leave out.

Runs the setup script for target under sudo, after confirming. Each script is idempotent, so re-running an install is safe.

usage

sys-setup
[target=chrome|dev-env|upgrade-service] (asked)
[uninstall=true|--uninstall]
[show_help=true|--show-help]

arguments

argument description
target Which resource to act on: 'chrome', 'dev-env' or 'upgrade-service'. Offered as a menu when omitted.
uninstall Uninstall the target instead of installing it. Defaults to False.
show_help Print the target script's own help and stop, doing nothing else. Defaults to False.

Defined in solver.utils.scripts.sys_setup.


Command: tags

Report over the central tag vocabulary (blogs/tags.json).

  • ⚑ needs reader or above.

The vocabulary's own shape, not any one problem's tags: how many tags each facet (domain, technique, takeaway) defines, and — with --details — how evenly the problems are spread across them, which is what shows a tag that has grown too broad or too narrow to be worth an article. Use blog <name> for one tag's problems.

usage

tags
[details=true|--details]

arguments

argument description
details Add, per facet, a histogram of how many problems each tag maps to — min / median / max, then binned counts. Defaults to False, which prints just the number of tags in each facet.

Defined in solver.core.tags.tags.


Command: test-cases

List a problem's test cases.

  • ⚑ needs reader or above.
  • ❏ uses/sets current problem.

Reads the problem's test_cases.json and prints one line per case — category, arguments and expected answer — for the categories asked for. URL arguments are abbreviated to their final path segment.

usage

test-cases
[problem=<n>] (default current)
[all|dev|main|extra ...]

arguments

argument description
problem The problem whose test cases to list.
*categories Test-case categories to include: 'dev', 'main', 'extra', or 'all' (which expands to all three). Defaults to all three.

Defined in solver.core.test_cases.test_cases.


Command: tool-setup

Install, uninstall or reinstall a per-user tool in your own home.

  • ⚑ needs contributor or above.
  • ✎ asks for anything you leave out.

Runs the tool's setup script as you — no sudo: the tool lands under your home (~/.local/bin), with your own login and config, so the floor is contributor, not admin like sys-setup. On the web host the download goes through the egress proxy. An install of a tool already there is a no-op; uninstall and reinstall ask first.

usage

tool-setup
[tool=claude] (asked)
[action=install|uninstall|reinstall] (default install)

arguments

argument description
tool Which tool: 'claude' (Claude Code). Offered as a menu when omitted.
action 'install', 'uninstall', or 'reinstall' (uninstall, then install). Defaults to 'install'.

Defined in solver.utils.scripts.tool_setup.


Command: update-docs

Rebuild the registry-generated blocks in the docs/ guides and the README.

  • ⚑ needs admin.
  • » supports --silent to suppress output.

Rewrites only the marked <!-- GEN:... --> sections — the command catalogue, the in-index summary, the per-command reference, the authorization audit table in docs/authorizations.md (module / command / channels / requires / least profile), and the README package-layout tree (built from each module's docstring) — from the live command registry and the source tree, leaving all hand-written prose untouched. Also regenerates the web start page's summary (packages/site/euler_site/content/home-summary.md) from the README's HOME slice. Run it after changing any command's name, alias, help text, signature, requires/channels, a module's first docstring line, or the README's HOME region.

What it rewrote is committed — the guides, the README, the module registry, the start-page summary and the Claude guidance, and nothing beside them.

Admin-floored on purpose, not out of caution. Registration is itself profile-filtered (packages/solver/solver/shell/command.py): a command above your floor is never registered, so registry.all() returns only what you may run. Regenerating from a lesser profile's registry would drop every admin-floored command from the audit table and the command index — silently, and in exactly the documents whose worth is being complete. The floor is what keeps the generated view whole.

For the same reason it runs from the shell with every command loaded: where the web host's shell is installed, that is solver-host, and a plain solver refuses.

usage

update-docs
[check=true|--check]
[silent=true|--silent]

arguments

argument description
check Write nothing and fail if any doc is out of date, listing the stale files. Defaults to False, which rewrites the docs in place and reports which changed.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.utils.update_doc.update_docs.


Command: update-models

Refresh the model catalogue and its pricing.

  • ⚑ needs admin.
  • » supports --silent to suppress output.

Lists the available Claude models from the Anthropic Models API, scrapes each model's base input/output price (per million tokens) from the public pricing page, and rewrites the # GEN:models block in models.py — the enum members, their inline comments, and the price map. Curated per-model comments are kept; a newly discovered model is commented with its display name. Nothing outside the markers is touched, and the USD→EUR rate is update-usd-rate's to refresh.

The header's date says when the catalogue last changed, not when it was last looked at: a run that finds nothing new writes nothing, so --check passes on any day the models and their prices have not moved.

A regenerated block is committed, staging models.py and nothing beside it.

usage

update-models
[check=true|--check]
[silent=true|--silent]

arguments

argument description
check Write nothing and fail if the generated block is out of date. Defaults to False, which rewrites it in place.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.ai.update_models.update_models.


Command: update-presentation-data

Refresh the presentation deck's data from the progress file.

  • ⚑ needs maintainer or above.
  • » supports --silent to suppress output.

Rebuilds docs/presentation/data/history.js — the script the deck at /story/ loads — from solutions/problems.json, the article index, the command catalogue and counts of the solution files. Only public metadata is exported: never an answer, and nothing read from inside solutions/private/.

The generation date and commit count are refreshed only alongside a real change, never on their own, so a current file is left alone.

A file that changed is committed, staging that one file and nothing beside it.

usage

update-presentation-data
[check=true|--check]
[silent=true|--silent]

arguments

argument description
check Write nothing and fail if the data file is out of date. Defaults to False, which refreshes it in place.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.utils.presentation.update_presentation_data.


Command: update-tags

The glue for the double-entry tag graph.

  • ⚑ needs contributor or above.

Order (maintainer beats contributor): apply maintainer edits to the central vocabulary (vs HEAD) into the per-problem files first; promote new-tags proposals into the vocabulary; rebuild every central refs leg from the per-problem files; regenerate the blog articles' problem lists and rewrite the article index blogs/articles.json (one row per writable blog: title, status, written date, solved/problem counts, tags, refs, the solved problems a final page covered when written, and those it has not), and name the final articles those later solves have overtaken — the rewrite candidates. A problem with notes but no tags.json is reported, not created here — the claude-api tags target (or the skill) authors it.

The reconciled graph is committed — both legs together, and nothing beside them.

usage

update-tags
[check=true|--check]

arguments

argument description
check Write nothing: report unknown slugs, facet violations, unpromoted proposals, missing files and a stale article index, and fail if the graph is inconsistent. Defaults to False, which reconciles the graph in place.

Defined in solver.core.tags.update_tags.


Command: update-usd-rate

Refresh the USD→EUR rate used to report API costs.

  • ⚑ needs maintainer or above.
  • » supports --silent to suppress output.

Fetches the euro reference rate from the European Central Bank's daily feed and writes it to ecb_usd_rate in values.conf, the setting costs converts API spend with. A rate within a rounding step of the stored one is left alone. Nothing else is touched.

A rate that moved is committed, staging that one file and nothing beside it.

usage

update-usd-rate
[check=true|--check]
[silent=true|--silent]

arguments

argument description
check Write nothing and fail if the stored rate is out of date. Defaults to False, which refreshes it in place. The rate drifts daily, so --check will usually report it as stale.
silent Suppress this command's output; errors and the result line still show.

Defined in solver.ai.update_usd_rate.update_usd_rate.


Command: user

Show the solver user, the current identity, and whether it can decrypt.

  • ⚑ needs reader or above.

A key pair is created only when the identity file is truly absent (first run) or on an explicitly confirmed --regen. An id file that exists but cannot be read — the vault is locked, the session key is stale, the vault file was lost — is a vault failure to fix, never a reason to mint a new identity: replacing the key would silently orphan the real one, and with it any enc-key authorization it carries.

usage

user
[regen=true|--regen]

arguments

argument description
regen Replace the existing key pair with a fresh one, after confirmation, and re-wrap the master key to it. Defaults to False.

Defined in solver.crypto.keys.user.


Command: user-authorize (authorize)

Record someone's public key and send them half the master key.

  • ⚑ needs maintainer or above.
  • ✎ asks for anything you leave out.

target is either form of the same act, told apart by shape:

  • a 16-hex message id — the key-authorization request their user command filed (msg list shows them). The key and the requester come from that message, the grant is confirmed, the half is sent as its own message for them to msg act on, and the request is dismissed — it is worked, and a queue that keeps worked requests is a queue nobody trusts;
  • a 64-hex public key — the same act by hand, for a key that reached you some other way. identity names who to send it to.

Two things happen, and the first is the durable one. The public key is written to the roster (/etc/euler/roster/users.json), which is what a later rotation re-issues against and what every shell — terminal or web — can read without sudo. Then delivery goes through key-split: half the master key, sealed to that public key, against the half the repository already carries. So a grant is never one artefact — the message, the private key that opens it and a current clone are all needed, and the recipient's own key-reconstruct proves the result before it writes anything.

That is why this needs the repository's half to be committed and pushed first: it refuses rather than sending a half nobody can complete. key-split on its own lays that half down. Aliased as authorize.

usage

user-authorize
[target=<str>] (asked)
[identity=<str>] (asked)

arguments

argument description
target The 16-hex id of a key-authorization message, or a 64-hex public key. Offered as a menu of the waiting requests when omitted; not strict, so a bare key can still be pasted.
identity Who the key belongs to. Taken from the thread for the message form, so it is only asked for the bare-key form — where there is nobody to send it to otherwise.

Defined in euler_webshell.shell.keys.user_authorize — on the web host only (the web shell and solver-host).


Command: users

Administer accounts on the authorization map + the auth service.

  • ⚑ needs admin.
  • ✎ asks for anything you leave out.

The whole command is admin-floored and the account verbs re-execute the admin CLI under sudo (the SoR + admin socket are root-only). There is no reader/maintainer tier here — a web shell cannot get sudo, so nothing runs over the web.

Every account verb writes both places it needs to: the host's system of record (profile, SRP state) through the sudo'd admin CLI, and the roster (/etc/euler/roster/users.json) for the parts every rung must be able to read without sudo. There is no separate registration step and no sweep to remember: a public key is recorded by the grant that issues one (user-authorize), and list says so when the two have drifted.

usage

users
[action=list|process-requests|add|change|enable|disable|remove|redeploy] (default list)
[identity=<str>] (asked)
[profile=reader|contributor|maintainer|admin] (default reader)

arguments

argument description
action What to do — list the roster, pending invites and the invite-request queue; process-requests walks that queue interactively (accept / ignore / dismiss each); add a map entry (@email also provisions the account and mints an invite, a bare os-login is local-only); change reassigns a profile; enable / disable the web SRP state; remove drops the account or entry; redeploy re-asserts the per-user host layer and re-lays every collaborator's git hooks, dropping live shells. Defaults to list.
identity Whose account to act on: a web email (with @) or a local OS login. Offered as a menu of the host's accounts (e-mail or login, with slug and profile) for the verbs that act on an account that already exists — change / enable / disable / remove — and not asked for the others, since add names one that does not yet and list / process-requests / redeploy name none.
profile The profile to assign, for add / change. admin is valid only for a local os-login, never a web account.

Defined in euler_webshell.users.users — on the web host only (the web shell and solver-host).


Command: vault

Encrypt this user's id + env at rest under a password-derived vault key.

  • ⚑ needs reader or above.
  • status (default): show whether the vault exists, which secret files are encrypted, and whether this session can decrypt them.
  • init: create the vault and migrate the existing plaintext id/env into it in place, then unlock the current session. Prompts for a new password.
  • unlock: unlock a locked session (the shell asks at startup; this is the retry — after a typo, or once you have the password to hand).
  • change-password: set a new password and re-key the vault: a fresh vault key, with the secrets re-encrypted under it. Other open shells must be restarted.

The password is never stored: set $EULER_VAULT_PASSWORD for a non-interactive unlock (a script, CI), otherwise you are asked once per shell.

usage

vault
[action=status|init|unlock|change-password] (default status)

arguments

argument description
action Which of the four operations above to run. Defaults to 'status'.

Defined in solver.crypto.keys.vault.


Command: version

Print the installed build version, plus live git detail of the clone.

  • ⚑ needs reader or above.

The first line is the running build: the number recorded in the tracked packages/solver/solver/version.py (config.version), written only by the release script and correct even in the deployed venv where there is no git. The second line is the live git describe of config.root_dir — the developer's checkout in a terminal, or the collaborator's own ~/euler clone in the web shell — reporting its exact commit and dirty state. The two answer different questions (what build is installed vs. what commit this clone sits on) and can legitimately differ.

Reader-floor and read-only: no writes, no network, no vault access — safe to run in every collaborator's long-lived web shell.

usage

version

Defined in solver.utils.version.version.