ReaperShell is a .NET-based interactive shell for developers who want live, local, scriptable tooling. It ships with practical built-in commands, supports hot-loadable command packs, and can manage Git-backed command-pack repos without leaving the shell.
ReaperShell is a console shell built on .NET 10. It combines familiar shell-style commands with a plugin model that loads command packs as regular .NET projects.
It is designed for:
- scripting local workflows
- experimenting with shell UX
- building custom developer tools in C#, F#, or VB.NET
- loading, rebuilding, and reloading command packs while you iterate
It is intentionally lightweight and experimental. ReaperShell is not a sandbox, and trusted command packs run in-process with your user privileges.
ReaperShell is useful when you want:
- shell commands written in C#
- fast iteration on local automation
- command packs that are normal SDK-style .NET projects
- live reload of custom tooling without restarting the host
- a small developer playground for shell behavior and command design
ReaperShell has been shaped by real command packs more than toy demos.
One early proving ground was iis-tools, which started as a small PowerShell log-searching script. After the original script was accidentally lost, it was rebuilt as a C# command and then split into a proper ReaperShell command pack. That work exposed the rough edges that mattered in practice: loading external commands, rebuilding them quickly, switching command-pack branches, proving which DLL was actually loaded, and surfacing useful repo and reload diagnostics.
That is the kind of workflow ReaperShell is meant to support: small local tools that can start as quick scripts, grow into structured commands, and still stay easy to load, test, rebuild, and improve.
- .NET 10 SDK
- Git for
repo add,repo sync,repo pull,repo switch,repo publish, and other Git-backed workflows - GitHub CLI
ghforrepo publish - A configured editor if you want
editto open files, or a usable fallback such ascode
Runtime notes:
- The project targets
net10.0. - The shell uses standard .NET console APIs and should run anywhere .NET 10 runs, but the repo is exercised on Windows and some conveniences are Windows-aware.
- ReaperShell is not a sandbox.
Clone the repo, build it, and start the shell:
git clone https://github.com/JoyfulReaper/ReaperShell.git
cd ReaperShell
dotnet build ReaperShell.slnx
dotnet run --project src/ReaperShellRun a one-off command:
dotnet run --project src/ReaperShell -- --command "repo list"Run a script:
dotnet run --project src/ReaperShell -- --script path\to\script.rshUseful execution flags:
--command <command>runs one command and exits--script <path>runs commands from a script file and exits--continue-on-errorkeeps a script running after a failure--profile <path>runs a specific startup profile instead of the default profile--no-profiledisables profile execution--state-dir <path>storessettings.jsonand managed repos under another directory
--command and --script are mutually exclusive.
Try this in an interactive shell:
rsh> help
rsh> pwd
rsh> ls
rsh> echo hello
rsh> repo list
To try a sample command pack:
rsh> repo add sample ./sample-packs/hello-pack
rsh> repo trust sample
rsh> repo build sample
rsh> repo load sample
rsh> hello
The commands below are currently registered by the shell. Syntax is kept close to the code so it matches the actual parser behavior.
| Command | Purpose | Syntax | Example |
|---|---|---|---|
help |
Lists registered commands and descriptions. | help |
help |
clear |
Clears the screen. | clear |
clear |
exit / quit |
Exits the shell. | exit |
quit |
version |
Prints shell, runtime, OS, and process information. | version |
version |
pwd |
Prints the current working directory. | pwd |
pwd |
ls |
Lists files and directories. | ls [path] |
ls sample-packs |
cd |
Changes the current working directory. | cd <path> |
cd sample-packs |
cat |
Prints a text file. | cat <file> |
cat README.md |
echo |
Prints its arguments joined by spaces. | echo [values...] |
echo hello world |
Interactive mode supports simple Tab completion for file and directory paths relative to the current working directory.
Interactive mode also shows the current shell working directory in the prompt by default, and the path updates after cd.
Interactive output uses simple semantic colors by default when the shell is attached to a console:
- green for success messages
- yellow for warnings
- red for errors
Color output follows ShellSettings.ColorMode, and redirected output stays plain text.
| Command | Purpose | Syntax | Example |
|---|---|---|---|
mkdir |
Creates one or more directories. | mkdir <path> [path...] |
mkdir scratch logs |
touch |
Creates files or updates their timestamps. | touch <file> [file...] |
touch notes.txt |
head |
Prints the first lines of a file. | head [-n <count>] <file> |
head -n 20 README.md |
tail |
Prints the last lines of a file. | tail [-n <count>] <file> |
tail -n 20 README.md |
grep |
Searches a text file for matching lines. | `grep [-i | --ignore-case] ` |
tree |
Prints a directory tree. | `tree [path] [-d | --directories-only]` |
open |
Opens a file, directory, or URL with the OS default handler. | open <path-or-url> |
open https://github.com/JoyfulReaper/ReaperShell |
rm |
Removes files and directories. | `rm [-r | --recursive] [-f |
cp |
Copies files and directories. | `cp [-r | --recursive] |
mv |
Moves or renames files and directories. | mv <source> <destination> |
mv draft.txt notes.txt |
| Command | Purpose | Syntax | Example |
|---|---|---|---|
history |
Prints session history, or clears it. | history or history clear |
history clear |
env |
Lists and manages session-scoped environment overrides. | env, env get <name>, env set <name> <value>, env unset <name> |
env set FOO bar |
alias |
Lists and manages aliases. | alias, alias set <name> <replacement>, alias remove <name>, alias clear, alias show <name> |
alias set ll "ls -a" |
ritual |
Lists, creates, and runs ritual scripts. | ritual list, ritual run <name> [--continue-on-error], ritual path, ritual new <name> |
ritual run awaken |
hook |
Lists and manages shell event hooks. | hook list, hook add <event> <ritual-name>, hook remove <event> <ritual-name>, hook clear <event>, hook events |
hook add startup awaken |
command |
Lists and forges commands inside a command pack. | command templates, command list <repo>, `command new [--template <basic |
file |
which |
Shows where a command comes from. | which <command> |
which hello |
describe |
Prints details about a command. | describe <command> |
describe hello |
edit |
Opens a file, directory, or repo command area in the configured editor. | edit <path>, edit --repo <repo> [--command <name>] |
edit --repo tools --command hello-kyle |
source |
Shows or opens the source location for a command. | source <command> |
source hello |
banner |
Prints the shell banner again. | banner |
banner |
status |
Prints shell runtime status. | status |
status |
doctor |
Runs a focused environment self-check. | doctor [--verbose] |
doctor --verbose |
fortune |
Prints a small shell fortune. | fortune |
fortune |
pray |
Prints a pseudo-ritual shell response. | pray |
pray |
reload |
Reloads settings and the active profile. | reload |
reload |
| Command | Purpose | Syntax | Example |
|---|---|---|---|
repo |
Manages command-pack repositories. | See the repo sections below. | repo list |
plugins |
Lists loaded command packs and their commands. | plugins |
plugins |
A command pack is a local folder or Git-backed repo that contains:
shellpack.json- one or more SDK-style command projects under the configured
commandsPath - compiled assemblies that implement
ReaperShell.Abstractions.IShellCommand
ReaperShell discovers commands from shellpack.json and the commandsPath entry. Command projects are normal .NET projects, not scripts.
Supported command project types:
*.csproj*.fsproj*.vbproj
Typical command pack layout:
tools/
shellpack.json
commands/
hello-kyle/
HelloKyleCommand.csproj
HelloKyleCommand.cs
Command packs reference ReaperShell.Abstractions and implement IShellCommand.
command new defaults to C#. Supported language aliases include cs and c# for C#, fs and f# for F#, and vbnet, visualbasic, and visual-basic for VB.NET. The available templates are basic, file, and process.
repo build <name>builds a trusted pack.repo load <name>loads a trusted pack.repo reload <name>unloads, rebuilds, and reloads the currently checked-out branch. It does not pull, rebase, or switch branches.repo build-all,repo load-all, andrepo reload-allrun the same operations across all trusted repos.
For Git-backed repos, repo build, repo reload, and repo reload-all print the current branch and short commit SHA before building so you can see exactly what is being rebuilt.
The repo ships with:
The multi-language pack demonstrates supported command-pack languages:
- C#
- F#
- VB.NET
ReaperShell tracks repo state in .rsh/settings.json by default. repo commands work with both local packs and Git-backed packs.
repo new <name> creates a managed local command pack and scaffolds a starter command.
It also creates a root .gitignore with standard .NET command-pack ignores so build artifacts do not get committed accidentally:
bin/obj/.vs/*.user*.suoTestResults/*.nupkg*.snupkg
When repo publish bootstraps a non-Git pack, it also makes sure that root .gitignore exists before the initial commit.
| Command | Purpose | Syntax | Example |
|---|---|---|---|
repo add |
Registers an existing local pack or clones a Git URL into managed state. | repo add <name> <path-or-git-url> |
repo add tools ./sample-packs/hello-pack |
repo list |
Lists registered repos. | repo list |
repo list |
repo remove |
Removes a repo from settings, optionally deleting managed files. | repo remove <name>, repo remove <name> --delete-files |
repo remove tools |
repo trust |
Marks a repo trusted, with optional autoload/profile behavior. | repo trust <name> [--autoload] [--load-now] [--profile] |
repo trust tools --autoload |
repo untrust |
Removes trust from an unloaded repo and clears autoload/profile integration. | repo untrust <name> |
repo untrust tools |
repo status |
Shows repo state and Git branch information. | repo status <name> |
repo status iis-tools |
repo branches |
Lists local and remote branches. | repo branches <name> |
repo branches iis-tools |
repo switch |
Switches branches, creating a tracking branch when needed. | repo switch <name> <branch> [--force] |
repo switch iis-tools dev |
repo pull |
Fast-forward-only pull of the current branch. | repo pull <name> |
repo pull iis-tools |
repo sync |
Compatibility alias for the same fast-forward-only pull behavior. | repo sync <name> |
repo sync iis-tools |
repo commit |
Stages and commits changes. | repo commit <name> "message" |
repo commit tools "Update command" |
repo push |
Pushes the repo. | repo push <name> |
repo push tools |
repo save |
Commits and then pushes. | repo save <name> "message" |
repo save tools "Add command" |
repo publish |
Bootstraps a local pack into GitHub using gh repo create. |
`repo publish <owner/repo> [--private | --public]` |
Branch management is explicit:
repo branches <name>shows local branches, remote branches, and the detected default remote branch.repo status <name>shows the current local branch, upstream branch, short commit SHA, dirty state, and available remote branches.repo switch <name> <branch>fetchesoriginfirst, then switches branches.- If the branch exists only remotely,
repo switchcreates a local tracking branch. - Passing
origin/devworks too; ReaperShell switches to a localdevtrackingorigin/dev. repo pull <name>usesgit pull --ff-only.repo sync <name>uses the samegit pull --ff-onlybehavior asrepo pull <name>.repo reload <name>never fetches, pulls, rebases, or switches branches.
Important behavior:
- Fetching a branch does not switch to it.
- ReaperShell does not switch branches implicitly during reload.
- Branch switching is always explicit.
repo switchrefuses to move a dirty working tree unless--forceis provided.--forceis conservative and only discards tracked changes when you explicitly ask for it.
When you run repo reload <name> or repo reload-all, ReaperShell prints the repo name, current branch, and short commit SHA before building. That makes it obvious what branch is actually being rebuilt.
Recommended workflow for updating a branch and then rebuilding it:
repo switch iis-tools dev
repo pull iis-tools
repo reload iis-tools
By default, ReaperShell stores runtime state under .rsh in the current working directory. You can move that with --state-dir <path>.
The default state directory contains:
settings.jsonprofile.rshrituals/repos/for managed repos created by ReaperShell
The interactive shell runs a startup profile by default.
- Default profile:
.rsh/profile.rsh - Explicit profile:
--profile <path> - Disable profiles:
--no-profile
Profiles are plain .rsh scripts.
Rituals are named .rsh scripts stored under .rsh/rituals/.
ritual listlists available ritual namesritual pathprints the rituals directoryritual new <name>creates a new ritualritual run <name>runs one ritualritual run <name> --continue-on-errorkeeps going after failures
Hooks connect shell events to rituals.
hook eventslists supported event nameshook add <event> <ritual-name>attaches a ritualhook listshows configured hookshook remove <event> <ritual-name>removes one ritual from one hookhook clear <event>clears one hook
Hooked rituals live in .rsh/rituals/ and are referenced by name.
Aliases are stored in .rsh/settings.json and expand before execution.
Examples:
rsh> alias set ll "ls -a"
rsh> alias show ll
rsh> alias remove ll
rsh> alias clear
Notes:
- Aliases are session-facing command replacements.
- Recursive alias loops are blocked.
- Built-in command names cannot be replaced with aliases.
ReaperShell keeps its runtime state outside the source tree by default.
Key files and directories:
.rsh/settings.jsonstores repos, aliases, hooks, editor choice, and external command mode.rsh/profile.rshis the default startup profile.rsh/rituals/stores ritual scripts.rsh/repos/stores managed local repos created byrepo newor some Git URL workflows
What to commit:
- commit source files, command-pack projects, manifests, and intentional pack content
- do not commit
.rsh/runtime state - do not commit
bin/andobj/build outputs
Generated command packs and Git-backed packs are scaffolded with a .gitignore that covers common .NET artifacts.
A ReaperShell command is a normal .NET class that implements IShellCommand.
Minimal C# example:
using ReaperShell.Abstractions;
namespace HelloCommand;
public sealed class HelloCommand : IShellCommand
{
public string Name => "hello";
public string Description => "Prints a hello message from a live-loaded command.";
public Task<int> ExecuteAsync(
ShellContext context,
IReadOnlyList<string> args,
CancellationToken cancellationToken = default)
{
context.WriteLine("Hello from sample command pack.");
return Task.FromResult(0);
}
}What matters:
Nameis the command nameDescriptionshows up inhelp- write normal output through
ShellContext.WriteLine - write errors through
ShellContext.WriteErrorLine - return
0for success and a non-zero exit code for failure
The repository includes sample packs under sample-packs/:
hello-packdemonstrates a simple C# command packmulti-language-packdemonstrates C#, F#, and VB.NET command projects
Typical usage:
rsh> repo add sample ./sample-packs/hello-pack
rsh> repo trust sample
rsh> repo build sample
rsh> repo load sample
rsh> hello
command not found: runhelp,plugins, orwhich <name>to see whether the command is built-in, loaded from a pack, or missing from PATHcommand pack does not load: make sure you ranrepo build <name>first and that the pack is trustedrepo is on the wrong branch: userepo status <name>andrepo branches <name>, thenrepo switch <name> <branch>reload did not update the command: verify you switched branches first, then runrepo reload <name>repo switchrefuses: the working tree has uncommitted changes; use--forceonly if you want to discard tracked changesgit branch exists remotely but not locally:repo switch <name> <branch>will create a tracking branch iforigin/<branch>existsplugin unload warning: unload is requested, not guaranteed, because collectible unload depends on references being releasedbuild fails: inspect the output fromrepo buildorrepo reload;doctor --verbosecan also help check the environmentmissing ReaperShell.Abstractions: make sure command projects reference the workspace copy ofReaperShell.Abstractionsscripts not running: check the script path, and remember that--continue-on-erroronly applies to script executionprofile path issues: verify.rsh/profile.rshexists, or use--profile <path>to point at a specific profile
Repository layout:
src/ReaperShell- host shell and built-in commandssrc/ReaperShell.Abstractions- the command-pack interface and shared abstractionstests/ReaperShell.Tests- unit and integration testssample-packs/- runnable example packs
Useful commands:
dotnet build ReaperShell.slnx
dotnet test tests/ReaperShell.Tests/ReaperShell.Tests.csproj
dotnet run --project src/ReaperShellIf you want to extend the shell:
- add a built-in command under
src/ReaperShell/BuiltIns - register it in
Program.RegisterBuiltIns - add a command-pack command by creating a project under a pack's
commands/directory and implementingIShellCommand
The codebase favors straightforward, explicit command handlers and direct .NET APIs over extra abstraction.
ReaperShell is an early experimental project. It is useful for local tooling, experimentation, and learning, but it is not trying to be a full general-purpose shell replacement.
Expect the surface area to evolve as command-pack workflows, repo workflows, and shell ergonomics mature.
MIT License. See LICENSE.