The $ command is a CLI tool that wraps any shell command and provides automatic logging, error reporting, issue creation capabilities, and natural language command aliases.
- Act as a transparent proxy for any shell command
- Pass all arguments directly to the underlying shell (bash/powershell/sh)
- Support all standard commands:
$ ls,$ cat,$ mkdir, etc. - Preserve exit codes from wrapped commands
- Support natural language command aliases via pattern matching
- Save full command output (stdout + stderr) to temporary OS directory
- Use platform-appropriate temp directory:
- Linux/macOS:
/tmp/oros.tmpdir() - Windows:
%TEMP%
- Linux/macOS:
- Log file naming:
start-command-{timestamp}-{random}.log
- Include timestamp at the start of logging
- Include timestamp at the end of logging
- Capture both stdout and stderr
- Store the complete command that was executed
- Store the exit code
- Print full log content to console after command finishes
- Always print the exit code (success or failure)
- Make exit code prominent as "it may usually be unclear"
- Detect if the executed command is a globally installed NPM package
- Use
which(Unix) orwhere(Windows) to find command location - Check if command resolves to a path within npm global modules
- Extract package name from the path
- Use
npm view <package> repository.urlto get repository URL
- Globally installed NPM packages (primary focus)
- Future: other package managers (pip, gem, etc.)
- Command must have failed (non-zero exit code)
- Repository must be detected for the command
ghCLI tool must be authenticated- User must have permission to create issues in target repository
- Use
gh-upload-logtool to upload the log file - Upload as a gist (for logs ≤100MB)
- Print the uploaded log URL to console
- Create an issue in the detected repository
- Issue title: Include command name and error summary
- Issue body: Include:
- Command that was executed
- Exit code
- Link to uploaded log
- System information (OS, Bun/Node.js version)
- Timestamp
- Print the created issue URL to console
- Still log everything to temp directory
- Still print logs and exit code to console
- Skip log upload and issue creation
- Still log everything to temp directory
- Still print logs and exit code to console
- Print a message that auto-reporting is skipped
- Skip log upload and issue creation
- Still log everything to temp directory
- Still print logs and exit code to console
- Print a message that log upload is skipped
- Skip issue creation (no log link available)
- Support natural language patterns defined in
substitutions.lino - Use Links Notation style with variables like
$packageName,$version - Match input against patterns and substitute with actual commands
- More specific patterns (more variables, longer patterns) take precedence
- Non-matching commands pass through unchanged
- NPM:
install $packageName npm package->npm install $packageName - NPM with version:
install $version version of $packageName npm package->npm install $packageName@$version - Git clone:
clone $repository repository->git clone $repository - Git checkout:
checkout $branch branch->git checkout $branch - Common operations:
list files->ls -la,show current directory->pwd
- Support user-defined patterns in
~/.start-command/substitutions.lino - User patterns take precedence over built-in patterns
- Support custom path via
START_SUBSTITUTIONS_PATHenvironment variable
Support two patterns for passing wrapper options:
$ [wrapper-options] -- [command] [command-options](explicit separator)$ [wrapper-options] command [command-options](options before command)
--isolated, -i <backend>: Run command in isolated environment--attached, -a: Run in attached/foreground mode (default)--detached, -d: Run in detached/background mode--session, -s <name>: Custom session name--image <image>: Docker image (required for docker backend)--isolated-user, -u [username]: Create new isolated user with same group permissions as current user--keep-user: Keep isolated user after command completes (don't delete)--keep-alive, -k: Keep isolation environment alive after command exits (disabled by default)--auto-remove-docker-container: Automatically remove docker container after exit (disabled by default, only valid with --isolated docker)
screen: GNU Screen terminal multiplexertmux: tmux terminal multiplexerdocker: Docker containers (requires --image option)
- Attached mode: Command runs in foreground, user can interact
- Detached mode: Command runs in background, session info displayed for reattachment
- Conflict handling: If both --attached and --detached are specified, show error asking user to choose one
--isolated-user, -u [username]: Create a new isolated user with same permissions- Creates user with same group memberships as current user (sudo, docker, wheel, etc.)
- Automatically generates username if not specified
- User is automatically deleted after command completes (unless
--keep-useris specified) - For screen/tmux: Wraps command with
sudo -n -u <username> - Requires sudo NOPASSWD for useradd/userdel commands
- Works with screen and tmux isolation (not docker)
--keep-user: Keep the isolated user after command completes- Only valid with
--isolated-useroption - User must be manually deleted later with
sudo userdel -r <username>
Example usage:
# Create isolated user and run command (user auto-deleted after)
$ --isolated-user -- npm test
# Custom username for isolated user
$ --isolated-user myrunner -- npm start
$ -u myrunner -- npm start
# Combine with screen isolation
$ --isolated screen --isolated-user -- npm test
# Combine with tmux detached mode
$ -i tmux -d --isolated-user testuser -- npm run build
# Keep user after command completes
$ --isolated-user --keep-user -- npm testBenefits:
- Clean user environment for each run
- Inherits sudo/docker access from current user
- Files created during execution belong to isolated user
- Automatic cleanup after execution (unless --keep-user)
By default, all isolation environments (screen, tmux, docker) automatically exit after the target command completes execution. This ensures:
- Resources are freed immediately after command execution
- No orphaned sessions/containers remain running
- Uniform behavior across all isolation backends
- Command output is still captured and logged before exit
The --keep-alive flag can be used to override this behavior and keep the isolation environment running after command completion, useful for debugging or interactive workflows.
Docker Container Filesystem Preservation:
By default, when using Docker isolation, the container filesystem is preserved after the container exits. This allows you to re-enter the container and access any files created during command execution, which is useful for retrieving additional output files or debugging. The container appears in docker ps -a in an "exited" state but is not removed.
The --auto-remove-docker-container flag enables automatic removal of the container after exit, which is useful when you don't need to preserve the container filesystem and want to clean up resources completely. When this flag is enabled:
- The container is removed immediately after exiting (using docker's
--rmflag) - The container will not appear in
docker ps -aafter command completion - You cannot re-enter the container to access files
- Resources are freed more aggressively
Note: --auto-remove-docker-container is only valid with --isolated docker and is independent of the --keep-alive flag.
- If isolation backend is not installed, show informative error with installation instructions
- If session creation fails, report error with details
- If sudo fails due to password requirement, command will fail with sudo error
START_DISABLE_AUTO_ISSUE: Disable automatic issue creationSTART_DISABLE_LOG_UPLOAD: Disable log uploadSTART_LOG_DIR: Custom log directorySTART_VERBOSE: Enable verbose outputSTART_DISABLE_SUBSTITUTIONS: Disable pattern matching/aliasesSTART_SUBSTITUTIONS_PATH: Custom path to substitutions file
The output uses a "timeline" format that is width-independent, lossless, and works uniformly in TTY, tmux, SSH, CI, and log files.
Format conventions:
│prefix → tool metadata (timeline marker)$prefix → executed command (can be virtual command for setup steps or user command)- No prefix → program output (stdout/stderr)
✓/✗→ result marker (success/failure)
When running commands in isolation environments that require setup steps (like Docker pulling an image), the timeline shows these as separate virtual commands. This makes the execution flow transparent:
$ docker pull alpine:latest ← virtual command (setup step)
Unable to find image 'alpine:latest' locally
... pull output ...
✓
│
$ echo hi ← user command
hi
✓
Virtual commands are only displayed when the setup step actually occurs (e.g., docker pull is shown only if the image needs to be downloaded).
│ session abc-123-def-456-ghi
│ start 2024-01-15 10:30:45
│
$ ls -la
... command output ...
✓
│ finish 2024-01-15 10:30:45
│ duration 0.123s
│ exit 0
│
│ log /tmp/start-command-1705312245-abc123.log
│ session abc-123-def-456-ghi
│ session abc-123-def-456-ghi
│ start 2024-01-15 10:30:45
│
$ failing-npm-command --arg
... command output/error ...
✗
│ finish 2024-01-15 10:30:46
│ duration 1.234s
│ exit 1
│
│ log /tmp/start-command-1705312246-def456.log
│ session abc-123-def-456-ghi
Detected repository: https://github.com/owner/repo
Log uploaded: https://gist.github.com/...
Issue created: https://github.com/owner/repo/issues/123
│ session abc-123-def-456-ghi
│ start 2024-01-15 10:30:45
│
$ unknown-command
... command output/error ...
✗
│ finish 2024-01-15 10:30:45
│ duration 0.050s
│ exit 127
│
│ log /tmp/start-command-1705312245-ghi789.log
│ session abc-123-def-456-ghi
Repository not detected - automatic issue creation skipped
- Bun >= 1.0.0 (primary runtime)
child_process(built-in)os(built-in)fs(built-in)path(built-in)
ghCLI - GitHub CLI for authentication and issue creationgh-upload-log- For uploading log files to GitHub
- Do not include sensitive environment variables in logs
- Do not include authentication tokens in issue reports
- Respect
.gitignorepatterns when detecting repositories - Only create issues in public repositories unless explicitly authorized
The $ command is implemented in both JavaScript and Rust. Both implementations must remain in sync:
- All CLI flags and options supported in JavaScript must also be supported in Rust
- All isolation backends (screen, tmux, docker, ssh) must behave identically in both implementations
- The substitution engine must produce identical results for the same input in both implementations
- Output format (timeline/spine format) must be identical between implementations
The following are enforced by CI/CD:
-
Minimum test coverage: Both JavaScript and Rust implementations must maintain at least 80% test coverage
- JavaScript: measured with Bun's built-in
--coverageflag - Rust: measured with
cargo-tarpaulin - CI/CD fails if coverage drops below 80% in either implementation
- JavaScript: measured with Bun's built-in
-
Test count parity: The Rust test count must be within 10% of the JavaScript test count
- CI/CD fails if Rust has ≥10% fewer test cases than JavaScript
- Checked by
scripts/check-test-parity.mjs - JavaScript test cases: count
it()calls injs/test/*.test.js - Rust test cases: count
#[test]macros inrust/tests/**/*.rsandrust/src/**/*.rs
When adding a new feature or fixing a bug in one implementation:
- Implement the same logic in the other implementation
- Add equivalent tests in both
js/test/andrust/tests/ - Ensure both implementations pass the parity check
- Update this document if the behavior changes
substitutions.lino— command alias patterns shared between both implementationsARCHITECTURE.md— documents the shared architectureREQUIREMENTS.md— this file, documents all requirements for both implementations