aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2025-06-19 09:37:44 +0100
committerdiogo464 <[email protected]>2025-06-19 09:37:44 +0100
commit2bdfafbfe14c300b63a7659e8ded45bad42c8208 (patch)
tree758ebccf04bb3e0748beea20e7fc85d22bf2ea51
parent000607fdd40887f3735281e0383d8f6c80a3d382 (diff)
Rename PidFileData to PidFile
- Rename struct and update all references throughout codebase - Cleaner, more intuitive name that follows Rust conventions - Update documentation comments - No functional changes, purely cosmetic refactor 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
-rw-r--r--IMPROVEMENT_PLAN_V2.md248
-rw-r--r--src/main.rs54
2 files changed, 275 insertions, 27 deletions
diff --git a/IMPROVEMENT_PLAN_V2.md b/IMPROVEMENT_PLAN_V2.md
new file mode 100644
index 0000000..abffa0e
--- /dev/null
+++ b/IMPROVEMENT_PLAN_V2.md
@@ -0,0 +1,248 @@
1# Demon CLI Improvement Plan v2
2
3## Overview
4This plan outlines four major improvements to enhance the demon CLI tool's usability, error handling, and documentation.
5
6## Task 1: Rename PidFileData to PidFile
7
8### Rationale
9- Shorter, cleaner name
10- More intuitive - it represents a PID file, not just data about one
11- Follows Rust naming conventions better
12
13### Implementation Steps
141. Rename struct `PidFileData` to `PidFile`
152. Update all references throughout the codebase
163. Update comments and documentation
174. Verify compilation and tests pass
18
19### Files to modify
20- `src/main.rs` - struct definition and all usages
21
22### Risk Assessment
23- **Low risk** - Simple rename refactor
24- No functional changes
25- All tests should continue to pass
26
27## Task 2: Implement PidFileReadError Enum
28
29### Rationale
30- Better error handling with specific error types
31- Eliminates redundant file existence checks
32- More idiomatic Rust error handling
33- Cleaner code in error handling paths
34
35### Design
36```rust
37#[derive(Debug)]
38pub enum PidFileReadError {
39 /// The PID file does not exist
40 FileNotFound,
41 /// The PID file exists but has invalid content
42 FileInvalid(String), // Include reason for invalidity
43 /// IO error occurred while reading
44 IoError(std::io::Error),
45}
46```
47
48### Implementation Steps
491. Define `PidFileReadError` enum with appropriate variants
502. Implement `Display` and `Error` traits for the enum
513. Update `PidFile::read_from_file()` to return `Result<PidFile, PidFileReadError>`
524. Update all call sites to handle the specific error types:
53 - `is_process_running()` - handle FileNotFound and FileInvalid as "not running"
54 - `stop_daemon()` - handle FileNotFound as "not running", FileInvalid as "cleanup needed"
55 - `list_daemons()` - handle FileInvalid as "INVALID" entry
56 - `status_daemon()` - handle FileNotFound as "NOT FOUND", FileInvalid as "ERROR"
57 - `clean_orphaned_files()` - handle FileInvalid as "needs cleanup"
585. Remove redundant `Path::new().exists()` checks where the error type provides this info
596. Test all error scenarios
60
61### Files to modify
62- `src/main.rs` - enum definition, read_from_file method, all usage sites
63
64### Risk Assessment
65- **Medium risk** - Changes error handling logic
66- Need thorough testing of error scenarios
67- Must ensure all edge cases are handled properly
68
69## Task 3: Make --id a Positional Argument
70
71### Analysis
72
73#### Current CLI Pattern
74```bash
75demon run --id web-server python -m http.server 8080
76demon stop --id web-server
77demon status --id web-server
78```
79
80#### Proposed CLI Pattern
81```bash
82demon run web-server python -m http.server 8080
83demon stop web-server
84demon status web-server
85```
86
87#### Pros
88- **Better UX**: More natural and concise
89- **Consistent with common tools**: Similar to git, docker, etc.
90- **Faster to type**: No --id flag needed
91- **More intuitive**: ID naturally comes first before the command
92
93#### Cons
94- **Breaking change**: Existing scripts/users need to update
95- **Potential ambiguity**: ID could be confused with command in some cases
96- **Parsing complexity**: Need careful handling of edge cases
97
98#### Design Decisions
991. **Make ID positional for all commands that currently use --id**
1002. **Keep -- separator support** for complex commands
1013. **Update help text** to reflect new usage
1024. **Maintain backward compatibility** by supporting both patterns initially (with deprecation warning)
103
104#### Commands to Update
105- `run <id> <command...>` - ID becomes first positional arg
106- `stop <id>` - ID becomes positional arg, remove timeout flag positioning issues
107- `tail <id>` - ID becomes positional arg
108- `cat <id>` - ID becomes positional arg
109- `status <id>` - ID becomes positional arg
110
111#### Implementation Strategy
1121. **Phase 1**: Support both patterns with deprecation warnings
1132. **Phase 2**: Remove old pattern support (future version)
114
115### Implementation Steps
1161. Define new argument structures with positional ID fields
1172. Update clap derive macros to make ID positional
1183. Update help text and documentation strings
1194. Add deprecation warnings for --id usage (optional)
1205. Update all internal function calls
1216. Update tests to use new CLI pattern
1227. Update LLM guide output
123
124### Files to modify
125- `src/main.rs` - argument structures, help text
126- `tests/cli.rs` - all test commands
127- LLM guide text
128
129### Risk Assessment
130- **High risk** - Breaking change for users
131- Need to update all tests
132- Must carefully verify argument parsing edge cases
133- Consider gradual migration strategy
134
135## Task 4: Write Comprehensive README.md
136
137### Target Audience
138- Developers who need background process management
139- LLM agents and their operators
140- DevOps engineers running long-term tasks
141- Anyone working with npm run dev, build processes, etc.
142
143### Content Structure
144```markdown
145# Demon - Background Process Manager
146
147## Overview
148Brief description focusing on core value proposition
149
150## Installation
151- `cargo install demon` (when published)
152- Building from source
153- System requirements
154
155## Quick Start
156- Basic examples
157- Common workflows
158
159## Use Cases
160- Development servers (npm run dev)
161- Background tasks and scripts
162- LLM agent process management
163- CI/CD pipeline tasks
164- Long-running computations
165
166## Command Reference
167- Complete command documentation
168- Examples for each command
169- Common flags and options
170
171## Integration with LLM Agents
172- How agents can use demon
173- Machine-readable output formats
174- Best practices for automation
175
176## Advanced Usage
177- File management
178- Process lifecycle
179- Troubleshooting
180- Performance considerations
181
182## Contributing
183- Development setup
184- Testing
185- Contribution guidelines
186```
187
188### Key Messages
1891. **Simplicity**: Easy background process management
1902. **Visibility**: Always know what's running and its status
1913. **Integration**: Built for automation and LLM agents
1924. **Reliability**: Robust process lifecycle management
193
194### Implementation Steps
1951. Research similar tools for README inspiration
1962. Write comprehensive content covering all sections
1973. Include practical examples and screenshots/command outputs
1984. Add badges for build status, crates.io, etc. (when applicable)
1995. Review and refine for clarity and completeness
200
201### Files to create
202- `README.md` - comprehensive documentation
203
204### Risk Assessment
205- **Low risk** - Documentation only
206- No functional changes
207- Easy to iterate and improve
208
209## Execution Order
210
2111. **Task 1**: Rename PidFileData to PidFile (Low risk, enables clean foundation)
2122. **Task 2**: Implement PidFileReadError enum (Medium risk, improves error handling)
2133. **Task 3**: Make --id positional (High risk, but significant UX improvement)
2144. **Task 4**: Write README.md (Low risk, improves project presentation)
215
216## Testing Strategy
217
218After each task:
2191. Run `cargo build` to ensure compilation
2202. Run `cargo test` to ensure all tests pass
2213. Manual testing of affected functionality
2224. Format code with `cargo fmt`
2235. Commit changes with descriptive message
224
225## Success Criteria
226
227- All tests pass after each change
228- No regressions in functionality
229- Improved error messages and handling
230- Better CLI usability
231- Comprehensive documentation
232- Clean, maintainable code
233
234## Rollback Plan
235
236Each task will be committed separately, allowing for easy rollback if issues arise:
2371. Git commit after each successful task
2382. If issues found, can revert specific commits
2393. Tests provide safety net for functionality
240
241## Timeline Estimate
242
243- Task 1: 15-20 minutes (straightforward refactor)
244- Task 2: 30-45 minutes (error handling logic)
245- Task 3: 45-60 minutes (CLI argument changes + tests)
246- Task 4: 30-45 minutes (documentation writing)
247
248Total: ~2-3 hours \ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index c82208b..4ab1dd9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,20 +11,20 @@ use std::time::Duration;
11 11
12/// Represents the contents of a PID file 12/// Represents the contents of a PID file
13#[derive(Debug, Clone)] 13#[derive(Debug, Clone)]
14struct PidFileData { 14struct PidFile {
15 /// Process ID 15 /// Process ID
16 pid: u32, 16 pid: u32,
17 /// Command that was executed (program + arguments) 17 /// Command that was executed (program + arguments)
18 command: Vec<String>, 18 command: Vec<String>,
19} 19}
20 20
21impl PidFileData { 21impl PidFile {
22 /// Create a new PidFileData instance 22 /// Create a new PidFile instance
23 fn new(pid: u32, command: Vec<String>) -> Self { 23 fn new(pid: u32, command: Vec<String>) -> Self {
24 Self { pid, command } 24 Self { pid, command }
25 } 25 }
26 26
27 /// Write PID file data to a file 27 /// Write PID file to a file
28 fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> { 28 fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
29 let mut file = File::create(path)?; 29 let mut file = File::create(path)?;
30 writeln!(file, "{}", self.pid)?; 30 writeln!(file, "{}", self.pid)?;
@@ -34,7 +34,7 @@ impl PidFileData {
34 Ok(()) 34 Ok(())
35 } 35 }
36 36
37 /// Read PID file data from a file 37 /// Read PID file from a file
38 fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Self> { 38 fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
39 let contents = std::fs::read_to_string(path)?; 39 let contents = std::fs::read_to_string(path)?;
40 let lines: Vec<&str> = contents.lines().collect(); 40 let lines: Vec<&str> = contents.lines().collect();
@@ -244,8 +244,8 @@ fn run_daemon(id: &str, command: &[String]) -> Result<()> {
244 .with_context(|| format!("Failed to start process '{}' with args {:?}", program, args))?; 244 .with_context(|| format!("Failed to start process '{}' with args {:?}", program, args))?;
245 245
246 // Write PID and command to file 246 // Write PID and command to file
247 let pid_data = PidFileData::new(child.id(), command.to_vec()); 247 let pid_file_data = PidFile::new(child.id(), command.to_vec());
248 pid_data.write_to_file(&pid_file)?; 248 pid_file_data.write_to_file(&pid_file)?;
249 249
250 // Don't wait for the child - let it run detached 250 // Don't wait for the child - let it run detached
251 std::mem::forget(child); 251 std::mem::forget(child);
@@ -261,14 +261,14 @@ fn is_process_running(pid_file: &str) -> Result<bool> {
261 return Ok(false); // No PID file means no running process 261 return Ok(false); // No PID file means no running process
262 } 262 }
263 263
264 let pid_data = match PidFileData::read_from_file(pid_file) { 264 let pid_file_data = match PidFile::read_from_file(pid_file) {
265 Ok(data) => data, 265 Ok(data) => data,
266 Err(_) => return Ok(false), // Invalid PID file 266 Err(_) => return Ok(false), // Invalid PID file
267 }; 267 };
268 268
269 // Check if process is still running using kill -0 269 // Check if process is still running using kill -0
270 let output = Command::new("kill") 270 let output = Command::new("kill")
271 .args(&["-0", &pid_data.pid.to_string()]) 271 .args(&["-0", &pid_file_data.pid.to_string()])
272 .output()?; 272 .output()?;
273 273
274 Ok(output.status.success()) 274 Ok(output.status.success())
@@ -278,7 +278,7 @@ fn stop_daemon(id: &str, timeout: u64) -> Result<()> {
278 let pid_file = format!("{}.pid", id); 278 let pid_file = format!("{}.pid", id);
279 279
280 // Check if PID file exists and read PID data 280 // Check if PID file exists and read PID data
281 let pid_data = match PidFileData::read_from_file(&pid_file) { 281 let pid_file_data = match PidFile::read_from_file(&pid_file) {
282 Ok(data) => data, 282 Ok(data) => data,
283 Err(_) => { 283 Err(_) => {
284 if Path::new(&pid_file).exists() { 284 if Path::new(&pid_file).exists() {
@@ -291,7 +291,7 @@ fn stop_daemon(id: &str, timeout: u64) -> Result<()> {
291 } 291 }
292 }; 292 };
293 293
294 let pid = pid_data.pid; 294 let pid = pid_file_data.pid;
295 295
296 tracing::info!( 296 tracing::info!(
297 "Stopping daemon '{}' (PID: {}) with timeout {}s", 297 "Stopping daemon '{}' (PID: {}) with timeout {}s",
@@ -599,19 +599,19 @@ fn list_daemons(quiet: bool) -> Result<()> {
599 let id = path_str.strip_suffix(".pid").unwrap_or(&path_str); 599 let id = path_str.strip_suffix(".pid").unwrap_or(&path_str);
600 600
601 // Read PID data from file 601 // Read PID data from file
602 match PidFileData::read_from_file(&path) { 602 match PidFile::read_from_file(&path) {
603 Ok(pid_data) => { 603 Ok(pid_file_data) => {
604 let status = if is_process_running_by_pid(pid_data.pid) { 604 let status = if is_process_running_by_pid(pid_file_data.pid) {
605 "RUNNING" 605 "RUNNING"
606 } else { 606 } else {
607 "DEAD" 607 "DEAD"
608 }; 608 };
609 609
610 if quiet { 610 if quiet {
611 println!("{}:{}:{}", id, pid_data.pid, status); 611 println!("{}:{}:{}", id, pid_file_data.pid, status);
612 } else { 612 } else {
613 let command = pid_data.command_string(); 613 let command = pid_file_data.command_string();
614 println!("{:<20} {:<8} {:<10} {}", id, pid_data.pid, status, command); 614 println!("{:<20} {:<8} {:<10} {}", id, pid_file_data.pid, status, command);
615 } 615 }
616 } 616 }
617 Err(_) => { 617 Err(_) => {
@@ -649,12 +649,12 @@ fn status_daemon(id: &str) -> Result<()> {
649 } 649 }
650 650
651 // Read PID data from file 651 // Read PID data from file
652 match PidFileData::read_from_file(&pid_file) { 652 match PidFile::read_from_file(&pid_file) {
653 Ok(pid_data) => { 653 Ok(pid_file_data) => {
654 println!("PID: {}", pid_data.pid); 654 println!("PID: {}", pid_file_data.pid);
655 println!("Command: {}", pid_data.command_string()); 655 println!("Command: {}", pid_file_data.command_string());
656 656
657 if is_process_running_by_pid(pid_data.pid) { 657 if is_process_running_by_pid(pid_file_data.pid) {
658 println!("Status: RUNNING"); 658 println!("Status: RUNNING");
659 659
660 // Show file information 660 // Show file information
@@ -696,13 +696,13 @@ fn clean_orphaned_files() -> Result<()> {
696 let id = path_str.strip_suffix(".pid").unwrap_or(&path_str); 696 let id = path_str.strip_suffix(".pid").unwrap_or(&path_str);
697 697
698 // Read PID data from file 698 // Read PID data from file
699 match PidFileData::read_from_file(&path) { 699 match PidFile::read_from_file(&path) {
700 Ok(pid_data) => { 700 Ok(pid_file_data) => {
701 // Check if process is still running 701 // Check if process is still running
702 if !is_process_running_by_pid(pid_data.pid) { 702 if !is_process_running_by_pid(pid_file_data.pid) {
703 println!( 703 println!(
704 "Cleaning up orphaned files for '{}' (PID: {})", 704 "Cleaning up orphaned files for '{}' (PID: {})",
705 id, pid_data.pid 705 id, pid_file_data.pid
706 ); 706 );
707 707
708 // Remove PID file 708 // Remove PID file
@@ -737,7 +737,7 @@ fn clean_orphaned_files() -> Result<()> {
737 tracing::info!( 737 tracing::info!(
738 "Skipping '{}' (PID: {}) - process is still running", 738 "Skipping '{}' (PID: {}) - process is still running",
739 id, 739 id,
740 pid_data.pid 740 pid_file_data.pid
741 ); 741 );
742 } 742 }
743 } 743 }