aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2025-06-19 09:46:52 +0100
committerdiogo464 <[email protected]>2025-06-19 09:46:52 +0100
commit6a4e906586a043dff295be532d653f4635974502 (patch)
tree4248bbdb6d3791d37ef8df42abc698f021a3e835
parent03793fad36480273ebd702e80f41f4baf513647c (diff)
Make --id a positional argument for improved CLI usability
- 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 <[email protected]>
-rw-r--r--src/main.rs71
-rw-r--r--tests/cli.rs41
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 {
144#[derive(Args)] 144#[derive(Args)]
145struct RunArgs { 145struct RunArgs {
146 /// Process identifier 146 /// Process identifier
147 #[arg(long)]
148 id: String, 147 id: String,
149 148
150 /// Command and arguments to execute 149 /// Command and arguments to execute
@@ -154,7 +153,6 @@ struct RunArgs {
154#[derive(Args)] 153#[derive(Args)]
155struct StopArgs { 154struct StopArgs {
156 /// Process identifier 155 /// Process identifier
157 #[arg(long)]
158 id: String, 156 id: String,
159 157
160 /// Timeout in seconds before sending SIGKILL after SIGTERM 158 /// Timeout in seconds before sending SIGKILL after SIGTERM
@@ -165,7 +163,6 @@ struct StopArgs {
165#[derive(Args)] 163#[derive(Args)]
166struct TailArgs { 164struct TailArgs {
167 /// Process identifier 165 /// Process identifier
168 #[arg(long)]
169 id: String, 166 id: String,
170 167
171 /// Only tail stdout 168 /// Only tail stdout
@@ -180,7 +177,6 @@ struct TailArgs {
180#[derive(Args)] 177#[derive(Args)]
181struct CatArgs { 178struct CatArgs {
182 /// Process identifier 179 /// Process identifier
183 #[arg(long)]
184 id: String, 180 id: String,
185 181
186 /// Only show stdout 182 /// Only show stdout
@@ -202,7 +198,6 @@ struct ListArgs {
202#[derive(Args)] 198#[derive(Args)]
203struct StatusArgs { 199struct StatusArgs {
204 /// Process identifier 200 /// Process identifier
205 #[arg(long)]
206 id: String, 201 id: String,
207} 202}
208 203
@@ -843,10 +838,10 @@ Demon is a command-line tool for spawning, managing, and monitoring background p
843 838
844## Available Commands 839## Available Commands
845 840
846### demon run --id <identifier> <command...> 841### demon run <identifier> <command...>
847Spawns a background process with the given identifier. 842Spawns a background process with the given identifier.
848 843
849**Syntax**: `demon run --id <id> [--] <command> [args...]` 844**Syntax**: `demon run <id> [--] <command> [args...]`
850 845
851**Behavior**: 846**Behavior**:
852- Creates `<id>.pid`, `<id>.stdout`, `<id>.stderr` files 847- Creates `<id>.pid`, `<id>.stdout`, `<id>.stderr` files
@@ -857,15 +852,15 @@ Spawns a background process with the given identifier.
857 852
858**Examples**: 853**Examples**:
859```bash 854```bash
860demon run --id web-server python -m http.server 8080 855demon run web-server python -m http.server 8080
861demon run --id backup-job -- rsync -av /data/ /backup/ 856demon run backup-job -- rsync -av /data/ /backup/
862demon run --id log-monitor tail -f /var/log/app.log 857demon run log-monitor tail -f /var/log/app.log
863``` 858```
864 859
865### demon stop --id <id> [--timeout <seconds>] 860### demon stop <id> [--timeout <seconds>]
866Stops a running daemon process gracefully. 861Stops a running daemon process gracefully.
867 862
868**Syntax**: `demon stop --id <id> [--timeout <seconds>]` 863**Syntax**: `demon stop <id> [--timeout <seconds>]`
869 864
870**Behavior**: 865**Behavior**:
871- Sends SIGTERM to the process first 866- Sends SIGTERM to the process first
@@ -876,8 +871,8 @@ Stops a running daemon process gracefully.
876 871
877**Examples**: 872**Examples**:
878```bash 873```bash
879demon stop --id web-server 874demon stop web-server
880demon stop --id backup-job --timeout 30 875demon stop backup-job --timeout 30
881``` 876```
882 877
883### demon list [--quiet] 878### demon list [--quiet]
@@ -903,10 +898,10 @@ backup-job:12346:DEAD
903- `RUNNING`: Process is actively running 898- `RUNNING`: Process is actively running
904- `DEAD`: Process has terminated, files still exist 899- `DEAD`: Process has terminated, files still exist
905 900
906### demon status --id <id> 901### demon status <id>
907Shows detailed status information for a specific daemon. 902Shows detailed status information for a specific daemon.
908 903
909**Syntax**: `demon status --id <id>` 904**Syntax**: `demon status <id>`
910 905
911**Output includes**: 906**Output includes**:
912- Daemon ID and PID file location 907- Daemon ID and PID file location
@@ -917,13 +912,13 @@ Shows detailed status information for a specific daemon.
917 912
918**Example**: 913**Example**:
919```bash 914```bash
920demon status --id web-server 915demon status web-server
921``` 916```
922 917
923### demon cat --id <id> [--stdout] [--stderr] 918### demon cat <id> [--stdout] [--stderr]
924Displays the contents of daemon log files. 919Displays the contents of daemon log files.
925 920
926**Syntax**: `demon cat --id <id> [--stdout] [--stderr]` 921**Syntax**: `demon cat <id> [--stdout] [--stderr]`
927 922
928**Behavior**: 923**Behavior**:
929- Shows both stdout and stderr by default 924- Shows both stdout and stderr by default
@@ -933,15 +928,15 @@ Displays the contents of daemon log files.
933 928
934**Examples**: 929**Examples**:
935```bash 930```bash
936demon cat --id web-server # Show both logs 931demon cat web-server # Show both logs
937demon cat --id web-server --stdout # Show only stdout 932demon cat web-server --stdout # Show only stdout
938demon cat --id web-server --stderr # Show only stderr 933demon cat web-server --stderr # Show only stderr
939``` 934```
940 935
941### demon tail --id <id> [--stdout] [--stderr] 936### demon tail <id> [--stdout] [--stderr]
942Follows daemon log files in real-time (like `tail -f`). 937Follows daemon log files in real-time (like `tail -f`).
943 938
944**Syntax**: `demon tail --id <id> [--stdout] [--stderr]` 939**Syntax**: `demon tail <id> [--stdout] [--stderr]`
945 940
946**Behavior**: 941**Behavior**:
947- Shows existing content first, then follows new content 942- Shows existing content first, then follows new content
@@ -952,8 +947,8 @@ Follows daemon log files in real-time (like `tail -f`).
952 947
953**Examples**: 948**Examples**:
954```bash 949```bash
955demon tail --id web-server # Follow both logs 950demon tail web-server # Follow both logs
956demon tail --id web-server --stdout # Follow only stdout 951demon tail web-server --stdout # Follow only stdout
957``` 952```
958 953
959### demon clean 954### demon clean
@@ -993,31 +988,31 @@ All files are created in the current working directory where `demon run` is exec
993 988
994### Starting a Web Server 989### Starting a Web Server
995```bash 990```bash
996demon run --id my-web-server python -m http.server 8080 991demon run my-web-server python -m http.server 8080
997demon status --id my-web-server # Check if it started 992demon status my-web-server # Check if it started
998demon tail --id my-web-server # Monitor logs 993demon tail my-web-server # Monitor logs
999``` 994```
1000 995
1001### Running a Backup Job 996### Running a Backup Job
1002```bash 997```bash
1003demon run --id nightly-backup -- rsync -av /data/ /backup/ 998demon run nightly-backup -- rsync -av /data/ /backup/
1004demon cat --id nightly-backup # Check output when done 999demon cat nightly-backup # Check output when done
1005demon clean # Clean up after completion 1000demon clean # Clean up after completion
1006``` 1001```
1007 1002
1008### Managing Multiple Services 1003### Managing Multiple Services
1009```bash 1004```bash
1010demon run --id api-server ./api --port 3000 1005demon run api-server ./api --port 3000
1011demon run --id worker-queue ./worker --config prod.conf 1006demon run worker-queue ./worker --config prod.conf
1012demon list # See all running services 1007demon list # See all running services
1013demon stop --id api-server # Stop specific service 1008demon stop api-server # Stop specific service
1014``` 1009```
1015 1010
1016### Monitoring and Debugging 1011### Monitoring and Debugging
1017```bash 1012```bash
1018demon list --quiet | grep RUNNING # Machine-readable active processes 1013demon list --quiet | grep RUNNING # Machine-readable active processes
1019demon tail --id problematic-app --stderr # Monitor just errors 1014demon tail problematic-app --stderr # Monitor just errors
1020demon status --id failing-service # Get detailed status 1015demon status failing-service # Get detailed status
1021``` 1016```
1022 1017
1023## Error Handling 1018## Error Handling
@@ -1040,12 +1035,12 @@ demon status --id failing-service # Get detailed status
1040### Scripting 1035### Scripting
1041```bash 1036```bash
1042# Check if service is running 1037# Check if service is running
1043if demon status --id my-service | grep -q "RUNNING"; then 1038if demon status my-service | grep -q "RUNNING"; then
1044 echo "Service is running" 1039 echo "Service is running"
1045fi 1040fi
1046 1041
1047# Start service if not running 1042# Start service if not running
1048demon list --quiet | grep -q "my-service:" || demon run --id my-service ./my-app 1043demon list --quiet | grep -q "my-service:" || demon run my-service ./my-app
1049 1044
1050# Get machine-readable process list 1045# Get machine-readable process list
1051demon list --quiet > process_status.txt 1046demon 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() {
35 35
36 let mut cmd = Command::cargo_bin("demon").unwrap(); 36 let mut cmd = Command::cargo_bin("demon").unwrap();
37 cmd.current_dir(temp_dir.path()) 37 cmd.current_dir(temp_dir.path())
38 .args(&["run", "--id", "test"]) 38 .args(&["run", "test"])
39 .assert() 39 .assert()
40 .failure() 40 .failure()
41 .stderr(predicate::str::contains("Command cannot be empty")); 41 .stderr(predicate::str::contains("Command cannot be empty"));
@@ -47,7 +47,7 @@ fn test_run_creates_files() {
47 47
48 let mut cmd = Command::cargo_bin("demon").unwrap(); 48 let mut cmd = Command::cargo_bin("demon").unwrap();
49 cmd.current_dir(temp_dir.path()) 49 cmd.current_dir(temp_dir.path())
50 .args(&["run", "--id", "test", "echo", "hello"]) 50 .args(&["run", "test", "echo", "hello"])
51 .assert() 51 .assert()
52 .success() 52 .success()
53 .stdout(predicate::str::contains("Started daemon 'test'")); 53 .stdout(predicate::str::contains("Started daemon 'test'"));
@@ -72,14 +72,14 @@ fn test_run_duplicate_process() {
72 // Start a long-running process 72 // Start a long-running process
73 let mut cmd = Command::cargo_bin("demon").unwrap(); 73 let mut cmd = Command::cargo_bin("demon").unwrap();
74 cmd.current_dir(temp_dir.path()) 74 cmd.current_dir(temp_dir.path())
75 .args(&["run", "--id", "long", "sleep", "30"]) 75 .args(&["run", "long", "sleep", "30"])
76 .assert() 76 .assert()
77 .success(); 77 .success();
78 78
79 // Try to start another with the same ID 79 // Try to start another with the same ID
80 let mut cmd = Command::cargo_bin("demon").unwrap(); 80 let mut cmd = Command::cargo_bin("demon").unwrap();
81 cmd.current_dir(temp_dir.path()) 81 cmd.current_dir(temp_dir.path())
82 .args(&["run", "--id", "long", "sleep", "5"]) 82 .args(&["run", "long", "sleep", "5"])
83 .assert() 83 .assert()
84 .failure() 84 .failure()
85 .stderr(predicate::str::contains("already running")); 85 .stderr(predicate::str::contains("already running"));
@@ -87,7 +87,7 @@ fn test_run_duplicate_process() {
87 // Clean up the running process 87 // Clean up the running process
88 let mut cmd = Command::cargo_bin("demon").unwrap(); 88 let mut cmd = Command::cargo_bin("demon").unwrap();
89 cmd.current_dir(temp_dir.path()) 89 cmd.current_dir(temp_dir.path())
90 .args(&["stop", "--id", "long"]) 90 .args(&["stop", "long"])
91 .assert() 91 .assert()
92 .success(); 92 .success();
93} 93}
@@ -114,7 +114,7 @@ fn test_list_with_processes() {
114 // Start a process 114 // Start a process
115 let mut cmd = Command::cargo_bin("demon").unwrap(); 115 let mut cmd = Command::cargo_bin("demon").unwrap();
116 cmd.current_dir(temp_dir.path()) 116 cmd.current_dir(temp_dir.path())
117 .args(&["run", "--id", "test", "echo", "done"]) 117 .args(&["run", "test", "echo", "done"])
118 .assert() 118 .assert()
119 .success(); 119 .success();
120 120
@@ -137,7 +137,6 @@ fn test_cat_output() {
137 cmd.current_dir(temp_dir.path()) 137 cmd.current_dir(temp_dir.path())
138 .args(&[ 138 .args(&[
139 "run", 139 "run",
140 "--id",
141 "test", 140 "test",
142 "--", 141 "--",
143 "sh", 142 "sh",
@@ -150,7 +149,7 @@ fn test_cat_output() {
150 // Cat the output 149 // Cat the output
151 let mut cmd = Command::cargo_bin("demon").unwrap(); 150 let mut cmd = Command::cargo_bin("demon").unwrap();
152 cmd.current_dir(temp_dir.path()) 151 cmd.current_dir(temp_dir.path())
153 .args(&["cat", "--id", "test"]) 152 .args(&["cat", "test"])
154 .assert() 153 .assert()
155 .success() 154 .success()
156 .stdout(predicate::str::contains("stdout line")) 155 .stdout(predicate::str::contains("stdout line"))
@@ -166,7 +165,6 @@ fn test_cat_stdout_only() {
166 cmd.current_dir(temp_dir.path()) 165 cmd.current_dir(temp_dir.path())
167 .args(&[ 166 .args(&[
168 "run", 167 "run",
169 "--id",
170 "test", 168 "test",
171 "--", 169 "--",
172 "sh", 170 "sh",
@@ -179,7 +177,7 @@ fn test_cat_stdout_only() {
179 // Cat only stdout 177 // Cat only stdout
180 let mut cmd = Command::cargo_bin("demon").unwrap(); 178 let mut cmd = Command::cargo_bin("demon").unwrap();
181 cmd.current_dir(temp_dir.path()) 179 cmd.current_dir(temp_dir.path())
182 .args(&["cat", "--id", "test", "--stdout"]) 180 .args(&["cat", "test", "--stdout"])
183 .assert() 181 .assert()
184 .success() 182 .success()
185 .stdout(predicate::str::contains("stdout line")) 183 .stdout(predicate::str::contains("stdout line"))
@@ -192,7 +190,7 @@ fn test_status_nonexistent() {
192 190
193 let mut cmd = Command::cargo_bin("demon").unwrap(); 191 let mut cmd = Command::cargo_bin("demon").unwrap();
194 cmd.current_dir(temp_dir.path()) 192 cmd.current_dir(temp_dir.path())
195 .args(&["status", "--id", "nonexistent"]) 193 .args(&["status", "nonexistent"])
196 .assert() 194 .assert()
197 .success() 195 .success()
198 .stdout(predicate::str::contains("NOT FOUND")); 196 .stdout(predicate::str::contains("NOT FOUND"));
@@ -205,14 +203,14 @@ fn test_status_dead_process() {
205 // Create a short-lived process 203 // Create a short-lived process
206 let mut cmd = Command::cargo_bin("demon").unwrap(); 204 let mut cmd = Command::cargo_bin("demon").unwrap();
207 cmd.current_dir(temp_dir.path()) 205 cmd.current_dir(temp_dir.path())
208 .args(&["run", "--id", "dead", "echo", "hello"]) 206 .args(&["run", "dead", "echo", "hello"])
209 .assert() 207 .assert()
210 .success(); 208 .success();
211 209
212 // Check its status (should be dead) 210 // Check its status (should be dead)
213 let mut cmd = Command::cargo_bin("demon").unwrap(); 211 let mut cmd = Command::cargo_bin("demon").unwrap();
214 cmd.current_dir(temp_dir.path()) 212 cmd.current_dir(temp_dir.path())
215 .args(&["status", "--id", "dead"]) 213 .args(&["status", "dead"])
216 .assert() 214 .assert()
217 .success() 215 .success()
218 .stdout(predicate::str::contains("DEAD")); 216 .stdout(predicate::str::contains("DEAD"));
@@ -224,7 +222,7 @@ fn test_stop_nonexistent() {
224 222
225 let mut cmd = Command::cargo_bin("demon").unwrap(); 223 let mut cmd = Command::cargo_bin("demon").unwrap();
226 cmd.current_dir(temp_dir.path()) 224 cmd.current_dir(temp_dir.path())
227 .args(&["stop", "--id", "nonexistent"]) 225 .args(&["stop", "nonexistent"])
228 .assert() 226 .assert()
229 .success() 227 .success()
230 .stdout(predicate::str::contains("not running")); 228 .stdout(predicate::str::contains("not running"));
@@ -237,14 +235,14 @@ fn test_stop_process() {
237 // Start a long-running process 235 // Start a long-running process
238 let mut cmd = Command::cargo_bin("demon").unwrap(); 236 let mut cmd = Command::cargo_bin("demon").unwrap();
239 cmd.current_dir(temp_dir.path()) 237 cmd.current_dir(temp_dir.path())
240 .args(&["run", "--id", "long", "sleep", "10"]) 238 .args(&["run", "long", "sleep", "10"])
241 .assert() 239 .assert()
242 .success(); 240 .success();
243 241
244 // Stop it 242 // Stop it
245 let mut cmd = Command::cargo_bin("demon").unwrap(); 243 let mut cmd = Command::cargo_bin("demon").unwrap();
246 cmd.current_dir(temp_dir.path()) 244 cmd.current_dir(temp_dir.path())
247 .args(&["stop", "--id", "long"]) 245 .args(&["stop", "long"])
248 .assert() 246 .assert()
249 .success() 247 .success()
250 .stdout(predicate::str::contains("terminated gracefully")); 248 .stdout(predicate::str::contains("terminated gracefully"));
@@ -272,7 +270,7 @@ fn test_clean_with_orphans() {
272 // Create a dead process 270 // Create a dead process
273 let mut cmd = Command::cargo_bin("demon").unwrap(); 271 let mut cmd = Command::cargo_bin("demon").unwrap();
274 cmd.current_dir(temp_dir.path()) 272 cmd.current_dir(temp_dir.path())
275 .args(&["run", "--id", "dead", "echo", "hello"]) 273 .args(&["run", "dead", "echo", "hello"])
276 .assert() 274 .assert()
277 .success(); 275 .success();
278 276
@@ -299,7 +297,6 @@ fn test_run_with_complex_command() {
299 cmd.current_dir(temp_dir.path()) 297 cmd.current_dir(temp_dir.path())
300 .args(&[ 298 .args(&[
301 "run", 299 "run",
302 "--id",
303 "complex", 300 "complex",
304 "--", 301 "--",
305 "sh", 302 "sh",
@@ -326,14 +323,14 @@ fn test_timeout_configuration() {
326 // Start a process 323 // Start a process
327 let mut cmd = Command::cargo_bin("demon").unwrap(); 324 let mut cmd = Command::cargo_bin("demon").unwrap();
328 cmd.current_dir(temp_dir.path()) 325 cmd.current_dir(temp_dir.path())
329 .args(&["run", "--id", "timeout-test", "sleep", "5"]) 326 .args(&["run", "timeout-test", "sleep", "5"])
330 .assert() 327 .assert()
331 .success(); 328 .success();
332 329
333 // Stop with custom timeout (should work normally since sleep responds to SIGTERM) 330 // Stop with custom timeout (should work normally since sleep responds to SIGTERM)
334 let mut cmd = Command::cargo_bin("demon").unwrap(); 331 let mut cmd = Command::cargo_bin("demon").unwrap();
335 cmd.current_dir(temp_dir.path()) 332 cmd.current_dir(temp_dir.path())
336 .args(&["stop", "--id", "timeout-test", "--timeout", "2"]) 333 .args(&["stop", "timeout-test", "--timeout", "2"])
337 .assert() 334 .assert()
338 .success() 335 .success()
339 .stdout(predicate::str::contains("terminated gracefully")); 336 .stdout(predicate::str::contains("terminated gracefully"));
@@ -349,7 +346,7 @@ fn test_invalid_process_id() {
349 // Status should handle it gracefully 346 // Status should handle it gracefully
350 let mut cmd = Command::cargo_bin("demon").unwrap(); 347 let mut cmd = Command::cargo_bin("demon").unwrap();
351 cmd.current_dir(temp_dir.path()) 348 cmd.current_dir(temp_dir.path())
352 .args(&["status", "--id", "invalid"]) 349 .args(&["status", "invalid"])
353 .assert() 350 .assert()
354 .success() 351 .success()
355 .stdout(predicate::str::contains("ERROR")); 352 .stdout(predicate::str::contains("ERROR"));
@@ -378,7 +375,7 @@ fn test_list_quiet_mode() {
378 // Create a process 375 // Create a process
379 let mut cmd = Command::cargo_bin("demon").unwrap(); 376 let mut cmd = Command::cargo_bin("demon").unwrap();
380 cmd.current_dir(temp_dir.path()) 377 cmd.current_dir(temp_dir.path())
381 .args(&["run", "--id", "quiet-test", "echo", "done"]) 378 .args(&["run", "quiet-test", "echo", "done"])
382 .assert() 379 .assert()
383 .success(); 380 .success();
384 381