Gamification of coding - execute any command with automatic logging and ability to auto-report issues on GitHub.
This repository contains two implementations that share the same behaviour and documentation:
- JavaScript / Bun — published to npm; release tags
js-v<version>, titles[JavaScript] <version>. - Rust — published to crates.io; release tags
rust-v<version>, titles[Rust] <version>.
Install using Bun:
bun install -g start-commandOr install the Rust version:
cargo install start-commandThe $ command acts as a wrapper for any shell command:
$ echo "Hello World"
$ ls -la
$ cat file.txt
$ bun test
$ git statusQuoted arguments keep their boundaries, so $ node -e "console.log('hi')" and
$ git commit -m "msg with spaces" reach the command exactly as typed. A single
quoted argument is still run as a shell script ($ 'cat file.txt | grep x').
See docs/USAGE.md for details.
When piping data to a command wrapped with $, put $ on the receiving command:
# Preferred - pipe TO the $-wrapped command
echo "hi" | $ agent
# Alternative - quote the entire pipeline (more verbose)
$ 'echo "hi" | agent'Both approaches work, but piping TO $ is simpler and requires fewer quotes.
# More examples
cat file.txt | $ processor
git diff | $ reviewer
echo "analyze this" | $ agent --verboseSee docs/PIPES.md for detailed guidance on piping, docs/USAGE.md for general usage, and docs/EXAMPLES.md for examples checked against the JavaScript and Rust CLIs.
You can also use natural language to execute common commands. The $ command supports pattern-based substitutions defined in substitutions.lino:
# Install NPM packages
$ install lodash npm package # -> npm install lodash
$ install 4.17.21 version of lodash npm package # -> npm install lodash@4.17.21
$ install lodash npm package globally # -> npm install -g lodash
# Clone repositories
$ clone https://github.com/user/repo repository # -> git clone https://github.com/user/repo
# Git operations
$ checkout main branch # -> git checkout main
$ create feature-x branch # -> git checkout -b feature-x
# Common operations
$ list files # -> ls -la
$ show current directory # -> pwd
$ create my-project directory # -> mkdir -p my-project
# Python packages
$ install requests python package # -> pip install requestsIf no pattern matches, the command is executed as-is.
Commands can be expressed in plain English using patterns defined in substitutions.lino. This file uses Links Notation style patterns with variables.
Each pattern is defined as a doublet link - a pair of pattern and replacement wrapped in parentheses:
# Pattern definition in substitutions.lino:
(
install $packageName npm package
npm install $packageName
)
# Usage:
$ install express npm package
# Executes: npm install express
Variables like $packageName, $version, $repository are captured and used in the substitution.
All command output is automatically saved to your system's temporary directory. Output uses a "timeline" format with clear visual distinction:
│ session abc-123-def-456-ghi
│ start 2024-01-15 10:30:45
│
$ bun test
... command output ...
✓
│ finish 2024-01-15 10:30:52
│ duration 7.456s
│ exit 0
│
│ log /tmp/start-command/logs/direct/abc-123-def-456-ghi.log
│ session abc-123-def-456-ghi
The │ prefix indicates tool metadata, $ shows the executed command, and ✓/✗ indicates success/failure.
Every command gets an execution record that can be queried later:
# Show one execution by UUID or isolation session name
$ --status abc-123-def-456-ghi
# List all stored executions, newest first
$ --list
# Machine-readable list output
$ --list --output-format json
# Only the executions that are still running
$ --list --running
# Upload the stored log for one execution
$ --upload-log 29d6c026-b168-44a6-8a3f-c3919c7e5327
# Ask a detached isolated execution to stop gracefully
$ --stop 29d6c026-b168-44a6-8a3f-c3919c7e5327
# Terminate a detached isolated execution immediately
$ --terminate 29d6c026-b168-44a6-8a3f-c3919c7e5327
# Re-enter a running detached session
$ --attach my-docker-session
# Follow its output without sending input
$ --attach my-docker-session --read-only
# Restart the stored command in the same environment
$ --resume my-docker-session
# Run a different command in the same container filesystem
$ --resume my-docker-session -- bash
# Re-attach or reconcile every execution still marked running
$ --resume-all--status and --list default to Links Notation. Both also support
--output-format json and --output-format text. Status and list output
include best-effort processIds for tracked wrapper processes and detached
screen, tmux, and Docker isolation containers when those native tools can
report them.
For detached Docker executions, oomKilled is reported as an observation of the
container cgroup flag, not as a verdict: while docker inspect still reports the
container as running the status stays executing, and once it stops the reported
exitCode is the container's real exit code. 137 is only used as a fallback
when the container is gone and neither a stored exit code nor a log footer can be
recovered.
--upload-log accepts either an execution UUID or an isolation session name. It
looks up the stored logPath, installs gh-upload-log with Bun or npm if the
uploader is missing, and then streams the uploader output directly.
--stop and --terminate accept either the execution UUID or the isolation
session/container name. --stop asks the backend to stop gracefully (CTRL+C for
screen/tmux, docker stop for Docker). --terminate uses the backend's
immediate termination command.
--attach, --resume and --resume-all accept the same identifiers as
--status: an execution UUID or an isolation session name.
--attach <id> re-enters a running detached session — docker attach,
screen -r, or tmux attach-session. Add --read-only to follow the output
without sending input (docker logs -f, tmux attach-session -r, or a log
tail). If the session is already gone, --attach says so and points at
--resume instead of leaving you with a docker exec command that cannot work
on a stopped container.
--resume <id> continues a stopped detached execution:
| Session state | What happens |
|---|---|
| Container exists, no new command | docker start re-runs the stored command in the same container. |
| Container exists, new command given | The container filesystem is committed to an image and a derived container runs the new command. |
| Session is gone | The command is launched again through the stored isolation options (same image, volumes, env, networks). |
--resume <id> -- <command> is the form downstream tools need: it runs a
different command against the same container filesystem, instead of
docker start -ai, which would re-run the original entrypoint from scratch.
A resume keeps the original execution UUID, so --status, --list and
--upload-log keep addressing one logical session across restarts. The previous
session name is remembered in sessionNameHistory and still resolves to the
same record.
--resume-all repairs state after the supervisor host restarts, which kills the
detached completion watcher while the container keeps running. Each execution
still marked running is reported with one of four actions:
| Action | Meaning |
|---|---|
reattached |
A live Docker container got a fresh completion watcher. |
running |
A live screen/tmux session needs nothing; its logging is in-session. |
reconciled |
The session is gone, so the record was finalized from its exit code/footer. |
unknown |
The backend cannot be probed locally (ssh); the record is left untouched. |
--resume-all never silently restarts work: continuing a command is always an
explicit, per-session decision made with --resume. Use --list --running as
the machine-readable set that drives it.
A bare exitCode 139 with oomKilled false hides the real cause. When the
stored log contains a fatal memory marker such as
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory,
--status adds a hint:
Exit Reason: memory-exhaustion (v8-heap-limit)
Memory Exhausted: true
Memory Evidence: FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
memoryExhausted answers the narrower question consumers of oomKilled are
really asking - did this run die of memory exhaustion? - and
memoryExhaustedReason carries the log line that proves it. A runtime that
aborts on its own heap limit dies below the container limit, so the kernel
never OOM-kills anything and State.OOMKilled stays false; the only evidence
is what the dying runtime printed into the log. Both fields appear only for a
non-zero exit code, so a command that merely prints such a marker and then
succeeds is never reported as a memory failure.
The same tail is scanned for attached and detached sessions alike, with a 64 KiB window, because V8 prints a long native stack trace after the marker.
exitReason, memoryExhausted and memoryExhaustedReason are only hints. They
never change status, exitCode or oomKilled, which stay observations of what
the backend actually reported.
The exit code is always prominently displayed after command completion, making it clear whether the command succeeded or failed.
When a command fails (non-zero exit code) and it's a globally installed NPM package:
- Repository Detection - Automatically detects the GitHub repository for NPM packages
- Log Upload - Uploads the full log to GitHub (requires gh-upload-log)
- Issue Creation - Creates an issue in the package's repository with:
- Command that was executed
- Exit code
- System information
- Link to uploaded log
│ session abc-123-def-456-ghi
│ start 2024-01-15 10:30:45
│
$ some-npm-tool --broken-arg
... error output ...
✗
│ finish 2024-01-15 10:30:46
│ duration 1.789s
│ exit 1
│
│ log /tmp/start-command/logs/direct/abc-123-def-456-ghi.log
│ session abc-123-def-456-ghi
Detected repository: https://github.com/owner/some-npm-tool
Log uploaded: https://gist.github.com/user/abc123
Issue created: https://github.com/owner/some-npm-tool/issues/42
Run commands in isolated environments using terminal multiplexers, containers, or remote servers:
# Run in tmux (attached by default)
$ --isolated tmux -- bun start
# Run in screen detached
$ --isolated screen --detached -- bun start
# Run in docker container
$ --isolated docker -- echo "hello from docker"
# Run a Bun command in the link-foundation/box JavaScript image
$ --isolated docker --image ghcr.io/link-foundation/box-js:latest -- bun --version
# Run a multi-runtime AI coding experiment in the full box image
$ --isolated docker --image ghcr.io/link-foundation/box:latest -- bash -lc 'node --version && python --version && rustc --version'
# Mount tool credentials and pass environment variables into the container
$ -i docker --image konard/hive-mind-dind:latest \
-v ~/.config/gh:/root/.config/gh \
-v ~/.claude:/root/.claude \
-e GH_TOKEN=$GH_TOKEN -- gh repo list
# Run a Docker-in-Docker image in privileged mode
$ -i docker --image konard/hive-mind-dind:latest --privileged -- solve <issue-url>
# Run on remote server via SSH
$ --isolated ssh --endpoint user@remote.server -- npm test
# Short form with custom session name
$ -i tmux -s my-session -d bun startCreate a new isolated user with the same group permissions as your current user to run commands in complete isolation:
# Create an isolated user with same permissions and run command
$ --isolated-user -- npm test
# Specify custom username for the isolated user
$ --isolated-user myrunner -- npm start
$ -u myrunner -- npm start
# Combine with process isolation (screen or tmux)
$ --isolated screen --isolated-user -- npm test
# Keep the user after command completes (don't delete)
$ --isolated-user --keep-user -- npm start
# The isolated user inherits your group memberships:
# - sudo group (if you have it)
# - docker group (if you have it)
# - wheel, admin, and other privileged groupsThe --isolated-user option:
- Creates a new system user with the same group memberships as your current user
- Runs the command as that user
- Automatically deletes the user after the command completes (unless
--keep-useris specified) - Requires sudo access without password (NOPASSWD configuration)
- Works with screen and tmux isolation environments (not docker)
This is useful for:
- Running untrusted code in isolation
- Testing with a clean user environment
- Ensuring commands don't affect your user's files
| Environment | Description | Installation |
|---|---|---|
screen |
GNU Screen terminal multiplexer | apt install screen / brew install screen |
tmux |
Modern terminal multiplexer | apt install tmux / brew install tmux |
docker |
Container isolation (uses a default image, or --image) |
Docker Installation |
ssh |
Remote execution via SSH (requires --endpoint) | apt install openssh-client / brew install openssh |
| Option | Description |
|---|---|
--isolated, --isolation, -i |
Isolation environment (screen, tmux, docker, ssh) |
--attached, -a |
Run in attached/foreground mode (default) |
--detached, -d |
Run in detached/background mode |
--session, -s |
Custom session/container name |
--image |
Docker image (optional; defaults to OS-matched image) |
--volume, -v |
Docker bind mount/volume host:container[:mode] (repeatable, docker only) |
--mount |
Docker --mount spec (repeatable, docker only) |
--env, -e |
Environment variable KEY=VALUE for the container (repeatable, docker only) |
--privileged |
Run docker container in privileged mode (docker only) |
--network |
Connect to a named network (repeatable, docker only) |
--network-alias |
Add an alias on the first network (repeatable, docker only) |
--endpoint |
SSH endpoint (required for ssh, e.g., user@host) |
--isolated-user, -u [name] |
Create isolated user with same permissions (screen/tmux) |
--keep-user |
Keep isolated user after command completes (don't delete) |
--keep-alive, -k |
Keep session alive after command completes |
--auto-remove-docker-container |
Always remove docker container after exit (docker only) |
--always-cleanup-container |
Always remove docker container after exit (docker only) |
--keep-container |
Keep docker container filesystem after exit (docker only) |
--keep-container-on-fail |
Keep failed or OOM-killed docker containers after exit (docker only) |
Note: Using both --attached and --detached together will result in an error - you must choose one mode.
When --network is repeated, Docker creates the container on the first network,
connects every additional network, and only then starts the command. This lets a
container retain egress through one network while reaching services on a private
network without a startup race. Repeated --network-alias values apply to the
first network.
$ --isolated docker --image alpine:3.23 \
--network bridge --network my-sidecar-net -- ping -c 1 sidecarBy default, all isolation environments (screen, tmux, docker) automatically exit after the target command completes. This ensures resources are freed immediately and provides uniform behavior across all backends.
Use --keep-alive (-k) to keep the session running after command completion:
# Default: session exits after command completes
$ -i screen -d -- echo "hello"
# Session will exit automatically after command completes.
# With --keep-alive: session stays running for interaction
$ -i screen -d -k -- echo "hello"
# Session will stay alive after command completes.
# You can reattach with: screen -r <session-name>For Docker containers, successful runs are removed by default. Failed containers, including containers Docker reports as OOMKilled, are kept for investigation and include a docker rm -f <container> cleanup hint. Use --always-cleanup-container or --auto-remove-docker-container to force removal after exit, or --keep-container to preserve the container filesystem after every run.
The tool works in any environment:
- No
ghCLI? - Logs are still saved locally, auto-reporting is skipped - No
gh-upload-logduring auto-reporting? - Issue can still be created with local log reference - No
gh-upload-logduring manual--upload-log? - The uploader is installed on demand - Repository not detected? - Command runs normally with logging
- No permission to create issue? - Skipped with a clear message
- Isolation environment not installed? - Clear error message with installation instructions
- Bun >= 1.0.0
- GitHub CLI (
gh) - For authentication and issue creation - gh-upload-log - For uploading log files
To set up auto-reporting:
# Install GitHub CLI and authenticate
gh auth login
# Install log uploader
bun install -g gh-upload-log- Command Execution - Your command is passed directly to the shell (bash/powershell/sh)
- Output Capture - Both stdout and stderr are captured while still being displayed
- Log File - Complete output is saved with timestamps and system info
- Failure Handling - On non-zero exit:
- Detects if the command is an NPM package
- Looks up the package's GitHub repository
- Uploads log (if
gh-upload-logis available) - Creates an issue (if
ghis authenticated and has permission)
The following environment variables can be used to customize behavior:
| Variable | Description |
|---|---|
START_DISABLE_AUTO_ISSUE |
Set to 1 or true to disable automatic issue creation |
START_DISABLE_LOG_UPLOAD |
Set to 1 or true to disable log upload |
START_LOG_DIR |
Custom directory for log files (defaults to OS temp directory) |
START_VERBOSE |
Set to 1 or true for verbose output |
START_DISABLE_SUBSTITUTIONS |
Set to 1 or true to disable pattern matching/aliases |
START_SUBSTITUTIONS_PATH |
Custom path to substitutions.lino file |
Example:
# Run without auto-issue creation
START_DISABLE_AUTO_ISSUE=1 $ bun test
# Use custom log directory
START_LOG_DIR=./logs $ bun test
# Disable substitutions (use raw command)
START_DISABLE_SUBSTITUTIONS=1 $ install lodash npm package
# Use custom substitutions file
START_SUBSTITUTIONS_PATH=/path/to/my-rules.lino $ install mypackage npm packageYou can create your own substitution patterns by placing a substitutions.lino file in ~/.start-command/substitutions.lino. User patterns take precedence over the default ones.
Log files are saved under /tmp/start-command/logs/ by default and contain the command output along with metadata. When an execution UUID is available, the log path is stable, for example /tmp/start-command/logs/direct/<uuid>.log or /tmp/start-command/logs/isolation/screen/<uuid>.log. The console output uses a "timeline" format:
│ session abc-123-def-456-ghi
│ start 2024-01-15 10:30:45
│
$ bun test
... command output ...
✓
│ finish 2024-01-15 10:30:52
│ duration 7.456s
│ exit 0
│
│ log /tmp/start-command/logs/direct/abc-123-def-456-ghi.log
│ session abc-123-def-456-ghi
The log file itself contains the raw command output and execution metadata.
Unlicense (public domain)
This project is released into the public domain under the Unlicense. It has fewer restrictions and more freedoms than MIT — especially for commercial use. You can copy, modify, publish, use, compile, sell, or distribute this software without any conditions or attribution requirements.