Skip to content

Latest commit

 

History

History
398 lines (281 loc) · 13.6 KB

File metadata and controls

398 lines (281 loc) · 13.6 KB

Requirements for $ Command (start-command)

Overview

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.

Core Requirements

1. Command Proxy Functionality

  • 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

2. Logging Requirements

2.1 Log Storage

  • Save full command output (stdout + stderr) to temporary OS directory
  • Use platform-appropriate temp directory:
    • Linux/macOS: /tmp/ or os.tmpdir()
    • Windows: %TEMP%
  • Log file naming: start-command-{timestamp}-{random}.log

2.2 Log Content

  • 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

2.3 Log Display

  • 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"

3. Repository Detection

3.1 NPM Package Detection

  • Detect if the executed command is a globally installed NPM package
  • Use which (Unix) or where (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.url to get repository URL

3.2 Supported Command Types

  • Globally installed NPM packages (primary focus)
  • Future: other package managers (pip, gem, etc.)

4. Automatic Issue Reporting (On Failure)

4.1 Preconditions for Auto-Reporting

  • Command must have failed (non-zero exit code)
  • Repository must be detected for the command
  • gh CLI tool must be authenticated
  • User must have permission to create issues in target repository

4.2 Log Upload

  • Use gh-upload-log tool to upload the log file
  • Upload as a gist (for logs ≤100MB)
  • Print the uploaded log URL to console

4.3 Issue Creation

  • 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

5. Graceful Degradation

5.1 When Repository Cannot Be Detected

  • Still log everything to temp directory
  • Still print logs and exit code to console
  • Skip log upload and issue creation

5.2 When gh Is Not Authenticated

  • 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

5.3 When gh-upload-log Is Not Installed

  • 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)

5. Command Aliases / Substitutions

5.1 Pattern Matching

  • 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

5.2 Built-in Patterns

  • 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

5.3 Custom Patterns

  • Support user-defined patterns in ~/.start-command/substitutions.lino
  • User patterns take precedence over built-in patterns
  • Support custom path via START_SUBSTITUTIONS_PATH environment variable

6. Process Isolation

6.1 Command Syntax

Support two patterns for passing wrapper options:

  • $ [wrapper-options] -- [command] [command-options] (explicit separator)
  • $ [wrapper-options] command [command-options] (options before command)

6.2 Isolation Options

  • --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)

6.3 Supported Backends

  • screen: GNU Screen terminal multiplexer
  • tmux: tmux terminal multiplexer
  • docker: Docker containers (requires --image option)

6.4 Mode Behavior

  • 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

6.5 User Isolation

  • --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-user is 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)

6.6 Keep User Option

  • --keep-user: Keep the isolated user after command completes
  • Only valid with --isolated-user option
  • 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 test

Benefits:

  • 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)

6.7 Auto-Exit Behavior

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 --rm flag)
  • The container will not appear in docker ps -a after 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.

6.8 Graceful Degradation

  • 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

Configuration Options

  • START_DISABLE_AUTO_ISSUE: Disable automatic issue creation
  • START_DISABLE_LOG_UPLOAD: Disable log upload
  • START_LOG_DIR: Custom log directory
  • START_VERBOSE: Enable verbose output
  • START_DISABLE_SUBSTITUTIONS: Disable pattern matching/aliases
  • START_SUBSTITUTIONS_PATH: Custom path to substitutions file

Output Format

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)

Virtual Commands

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).

Success Case

│ 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

Failure Case (With Auto-Reporting)

│ 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

Failure Case (Without Auto-Reporting)

│ 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

Dependencies

Required

  • Bun >= 1.0.0 (primary runtime)
  • child_process (built-in)
  • os (built-in)
  • fs (built-in)
  • path (built-in)

Optional (for full functionality)

  • gh CLI - GitHub CLI for authentication and issue creation
  • gh-upload-log - For uploading log files to GitHub

Security Considerations

  • Do not include sensitive environment variables in logs
  • Do not include authentication tokens in issue reports
  • Respect .gitignore patterns when detecting repositories
  • Only create issues in public repositories unless explicitly authorized

Dual-Language Sync Requirements

The $ command is implemented in both JavaScript and Rust. Both implementations must remain in sync:

Functional Parity

  • 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

Test Coverage Requirements

The following are enforced by CI/CD:

  1. Minimum test coverage: Both JavaScript and Rust implementations must maintain at least 80% test coverage

    • JavaScript: measured with Bun's built-in --coverage flag
    • Rust: measured with cargo-tarpaulin
    • CI/CD fails if coverage drops below 80% in either implementation
  2. 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 in js/test/*.test.js
    • Rust test cases: count #[test] macros in rust/tests/**/*.rs and rust/src/**/*.rs

Adding New Features

When adding a new feature or fixing a bug in one implementation:

  1. Implement the same logic in the other implementation
  2. Add equivalent tests in both js/test/ and rust/tests/
  3. Ensure both implementations pass the parity check
  4. Update this document if the behavior changes

Shared Resources

  • substitutions.lino — command alias patterns shared between both implementations
  • ARCHITECTURE.md — documents the shared architecture
  • REQUIREMENTS.md — this file, documents all requirements for both implementations