From 6a4e906586a043dff295be532d653f4635974502 Mon Sep 17 00:00:00 2001 From: diogo464 Date: Thu, 19 Jun 2025 09:46:52 +0100 Subject: Make --id a positional argument for improved CLI usability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove --id flags from all command argument structures - Update RunArgs, StopArgs, TailArgs, CatArgs, StatusArgs to use positional ID - Update all integration tests to use new positional argument syntax - Update comprehensive LLM guide documentation with new command syntax - Maintain -- separator support for complex commands - More natural CLI interface: 'demon run web-server python -m http.server 8080' - Consistent with common tools like docker, systemctl, git - Breaking change but improves usability significantly - All tests pass with new CLI format Examples of new syntax: - demon run web-server python -m http.server 8080 - demon stop web-server - demon status web-server - demon tail web-server --stdout - demon cat web-server 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/main.rs | 71 ++++++++++++++++++++++++++++-------------------------------- tests/cli.rs | 41 ++++++++++++++++------------------- 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/src/main.rs b/src/main.rs index 246af67..bedcb74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -144,7 +144,6 @@ enum Commands { #[derive(Args)] struct RunArgs { /// Process identifier - #[arg(long)] id: String, /// Command and arguments to execute @@ -154,7 +153,6 @@ struct RunArgs { #[derive(Args)] struct StopArgs { /// Process identifier - #[arg(long)] id: String, /// Timeout in seconds before sending SIGKILL after SIGTERM @@ -165,7 +163,6 @@ struct StopArgs { #[derive(Args)] struct TailArgs { /// Process identifier - #[arg(long)] id: String, /// Only tail stdout @@ -180,7 +177,6 @@ struct TailArgs { #[derive(Args)] struct CatArgs { /// Process identifier - #[arg(long)] id: String, /// Only show stdout @@ -202,7 +198,6 @@ struct ListArgs { #[derive(Args)] struct StatusArgs { /// Process identifier - #[arg(long)] id: String, } @@ -843,10 +838,10 @@ Demon is a command-line tool for spawning, managing, and monitoring background p ## Available Commands -### demon run --id +### demon run Spawns a background process with the given identifier. -**Syntax**: `demon run --id [--] [args...]` +**Syntax**: `demon run [--] [args...]` **Behavior**: - Creates `.pid`, `.stdout`, `.stderr` files @@ -857,15 +852,15 @@ Spawns a background process with the given identifier. **Examples**: ```bash -demon run --id web-server python -m http.server 8080 -demon run --id backup-job -- rsync -av /data/ /backup/ -demon run --id log-monitor tail -f /var/log/app.log +demon run web-server python -m http.server 8080 +demon run backup-job -- rsync -av /data/ /backup/ +demon run log-monitor tail -f /var/log/app.log ``` -### demon stop --id [--timeout ] +### demon stop [--timeout ] Stops a running daemon process gracefully. -**Syntax**: `demon stop --id [--timeout ]` +**Syntax**: `demon stop [--timeout ]` **Behavior**: - Sends SIGTERM to the process first @@ -876,8 +871,8 @@ Stops a running daemon process gracefully. **Examples**: ```bash -demon stop --id web-server -demon stop --id backup-job --timeout 30 +demon stop web-server +demon stop backup-job --timeout 30 ``` ### demon list [--quiet] @@ -903,10 +898,10 @@ backup-job:12346:DEAD - `RUNNING`: Process is actively running - `DEAD`: Process has terminated, files still exist -### demon status --id +### demon status Shows detailed status information for a specific daemon. -**Syntax**: `demon status --id ` +**Syntax**: `demon status ` **Output includes**: - Daemon ID and PID file location @@ -917,13 +912,13 @@ Shows detailed status information for a specific daemon. **Example**: ```bash -demon status --id web-server +demon status web-server ``` -### demon cat --id [--stdout] [--stderr] +### demon cat [--stdout] [--stderr] Displays the contents of daemon log files. -**Syntax**: `demon cat --id [--stdout] [--stderr]` +**Syntax**: `demon cat [--stdout] [--stderr]` **Behavior**: - Shows both stdout and stderr by default @@ -933,15 +928,15 @@ Displays the contents of daemon log files. **Examples**: ```bash -demon cat --id web-server # Show both logs -demon cat --id web-server --stdout # Show only stdout -demon cat --id web-server --stderr # Show only stderr +demon cat web-server # Show both logs +demon cat web-server --stdout # Show only stdout +demon cat web-server --stderr # Show only stderr ``` -### demon tail --id [--stdout] [--stderr] +### demon tail [--stdout] [--stderr] Follows daemon log files in real-time (like `tail -f`). -**Syntax**: `demon tail --id [--stdout] [--stderr]` +**Syntax**: `demon tail [--stdout] [--stderr]` **Behavior**: - Shows existing content first, then follows new content @@ -952,8 +947,8 @@ Follows daemon log files in real-time (like `tail -f`). **Examples**: ```bash -demon tail --id web-server # Follow both logs -demon tail --id web-server --stdout # Follow only stdout +demon tail web-server # Follow both logs +demon tail web-server --stdout # Follow only stdout ``` ### demon clean @@ -993,31 +988,31 @@ All files are created in the current working directory where `demon run` is exec ### Starting a Web Server ```bash -demon run --id my-web-server python -m http.server 8080 -demon status --id my-web-server # Check if it started -demon tail --id my-web-server # Monitor logs +demon run my-web-server python -m http.server 8080 +demon status my-web-server # Check if it started +demon tail my-web-server # Monitor logs ``` ### Running a Backup Job ```bash -demon run --id nightly-backup -- rsync -av /data/ /backup/ -demon cat --id nightly-backup # Check output when done +demon run nightly-backup -- rsync -av /data/ /backup/ +demon cat nightly-backup # Check output when done demon clean # Clean up after completion ``` ### Managing Multiple Services ```bash -demon run --id api-server ./api --port 3000 -demon run --id worker-queue ./worker --config prod.conf +demon run api-server ./api --port 3000 +demon run worker-queue ./worker --config prod.conf demon list # See all running services -demon stop --id api-server # Stop specific service +demon stop api-server # Stop specific service ``` ### Monitoring and Debugging ```bash demon list --quiet | grep RUNNING # Machine-readable active processes -demon tail --id problematic-app --stderr # Monitor just errors -demon status --id failing-service # Get detailed status +demon tail problematic-app --stderr # Monitor just errors +demon status failing-service # Get detailed status ``` ## Error Handling @@ -1040,12 +1035,12 @@ demon status --id failing-service # Get detailed status ### Scripting ```bash # Check if service is running -if demon status --id my-service | grep -q "RUNNING"; then +if demon status my-service | grep -q "RUNNING"; then echo "Service is running" fi # Start service if not running -demon list --quiet | grep -q "my-service:" || demon run --id my-service ./my-app +demon list --quiet | grep -q "my-service:" || demon run my-service ./my-app # Get machine-readable process list demon list --quiet > process_status.txt diff --git a/tests/cli.rs b/tests/cli.rs index 2903f61..5e72dad 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -35,7 +35,7 @@ fn test_run_missing_command() { let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["run", "--id", "test"]) + .args(&["run", "test"]) .assert() .failure() .stderr(predicate::str::contains("Command cannot be empty")); @@ -47,7 +47,7 @@ fn test_run_creates_files() { let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["run", "--id", "test", "echo", "hello"]) + .args(&["run", "test", "echo", "hello"]) .assert() .success() .stdout(predicate::str::contains("Started daemon 'test'")); @@ -72,14 +72,14 @@ fn test_run_duplicate_process() { // Start a long-running process let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["run", "--id", "long", "sleep", "30"]) + .args(&["run", "long", "sleep", "30"]) .assert() .success(); // Try to start another with the same ID let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["run", "--id", "long", "sleep", "5"]) + .args(&["run", "long", "sleep", "5"]) .assert() .failure() .stderr(predicate::str::contains("already running")); @@ -87,7 +87,7 @@ fn test_run_duplicate_process() { // Clean up the running process let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["stop", "--id", "long"]) + .args(&["stop", "long"]) .assert() .success(); } @@ -114,7 +114,7 @@ fn test_list_with_processes() { // Start a process let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["run", "--id", "test", "echo", "done"]) + .args(&["run", "test", "echo", "done"]) .assert() .success(); @@ -137,7 +137,6 @@ fn test_cat_output() { cmd.current_dir(temp_dir.path()) .args(&[ "run", - "--id", "test", "--", "sh", @@ -150,7 +149,7 @@ fn test_cat_output() { // Cat the output let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["cat", "--id", "test"]) + .args(&["cat", "test"]) .assert() .success() .stdout(predicate::str::contains("stdout line")) @@ -166,7 +165,6 @@ fn test_cat_stdout_only() { cmd.current_dir(temp_dir.path()) .args(&[ "run", - "--id", "test", "--", "sh", @@ -179,7 +177,7 @@ fn test_cat_stdout_only() { // Cat only stdout let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["cat", "--id", "test", "--stdout"]) + .args(&["cat", "test", "--stdout"]) .assert() .success() .stdout(predicate::str::contains("stdout line")) @@ -192,7 +190,7 @@ fn test_status_nonexistent() { let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["status", "--id", "nonexistent"]) + .args(&["status", "nonexistent"]) .assert() .success() .stdout(predicate::str::contains("NOT FOUND")); @@ -205,14 +203,14 @@ fn test_status_dead_process() { // Create a short-lived process let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["run", "--id", "dead", "echo", "hello"]) + .args(&["run", "dead", "echo", "hello"]) .assert() .success(); // Check its status (should be dead) let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["status", "--id", "dead"]) + .args(&["status", "dead"]) .assert() .success() .stdout(predicate::str::contains("DEAD")); @@ -224,7 +222,7 @@ fn test_stop_nonexistent() { let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["stop", "--id", "nonexistent"]) + .args(&["stop", "nonexistent"]) .assert() .success() .stdout(predicate::str::contains("not running")); @@ -237,14 +235,14 @@ fn test_stop_process() { // Start a long-running process let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["run", "--id", "long", "sleep", "10"]) + .args(&["run", "long", "sleep", "10"]) .assert() .success(); // Stop it let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["stop", "--id", "long"]) + .args(&["stop", "long"]) .assert() .success() .stdout(predicate::str::contains("terminated gracefully")); @@ -272,7 +270,7 @@ fn test_clean_with_orphans() { // Create a dead process let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["run", "--id", "dead", "echo", "hello"]) + .args(&["run", "dead", "echo", "hello"]) .assert() .success(); @@ -299,7 +297,6 @@ fn test_run_with_complex_command() { cmd.current_dir(temp_dir.path()) .args(&[ "run", - "--id", "complex", "--", "sh", @@ -326,14 +323,14 @@ fn test_timeout_configuration() { // Start a process let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["run", "--id", "timeout-test", "sleep", "5"]) + .args(&["run", "timeout-test", "sleep", "5"]) .assert() .success(); // Stop with custom timeout (should work normally since sleep responds to SIGTERM) let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["stop", "--id", "timeout-test", "--timeout", "2"]) + .args(&["stop", "timeout-test", "--timeout", "2"]) .assert() .success() .stdout(predicate::str::contains("terminated gracefully")); @@ -349,7 +346,7 @@ fn test_invalid_process_id() { // Status should handle it gracefully let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["status", "--id", "invalid"]) + .args(&["status", "invalid"]) .assert() .success() .stdout(predicate::str::contains("ERROR")); @@ -378,7 +375,7 @@ fn test_list_quiet_mode() { // Create a process let mut cmd = Command::cargo_bin("demon").unwrap(); cmd.current_dir(temp_dir.path()) - .args(&["run", "--id", "quiet-test", "echo", "done"]) + .args(&["run", "quiet-test", "echo", "done"]) .assert() .success(); -- cgit