| 557 | } |
| 558 | |
| 559 | func runWatchSubcommand(subCmd, root string) { |
| 560 | absRoot, err := filepath.Abs(root) |
| 561 | if err != nil { |
| 562 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) |
| 563 | os.Exit(1) |
| 564 | } |
| 565 | |
| 566 | switch subCmd { |
| 567 | case "start": |
| 568 | if watchIsRunning(absRoot) { |
| 569 | fmt.Println("Watch daemon already running") |
| 570 | return |
| 571 | } |
| 572 | // Fork a background daemon |
| 573 | exe, err := executablePath() |
| 574 | if err != nil { |
| 575 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) |
| 576 | os.Exit(1) |
| 577 | } |
| 578 | cmd := execCommand(exe, "watch", "daemon", absRoot) |
| 579 | cmd.Stdout = nil |
| 580 | cmd.Stderr = nil |
| 581 | cmd.Stdin = nil |
| 582 | // Detach from parent process group (Unix only) |
| 583 | setSysProcAttr(cmd) |
| 584 | if err := cmd.Start(); err != nil { |
| 585 | fmt.Fprintf(os.Stderr, "Error starting daemon: %v\n", err) |
| 586 | os.Exit(1) |
| 587 | } |
| 588 | fmt.Printf("Watch daemon started (pid %d)\n", cmd.Process.Pid) |
| 589 | |
| 590 | case "daemon": |
| 591 | // Internal: run as the actual daemon process |
| 592 | runDaemon(absRoot) |
| 593 | |
| 594 | case "stop": |
| 595 | if !watchIsRunning(absRoot) { |
| 596 | fmt.Println("Watch daemon not running") |
| 597 | return |
| 598 | } |
| 599 | if err := stopWatchDaemon(absRoot); err != nil { |
| 600 | if errors.Is(err, watch.ErrForeignDaemonPID) { |
| 601 | fmt.Println("Watch daemon not running (cleared stale PID file)") |
| 602 | return |
| 603 | } |
| 604 | fmt.Fprintf(os.Stderr, "Error stopping daemon: %v\n", err) |
| 605 | os.Exit(1) |
| 606 | } |
| 607 | fmt.Println("Watch daemon stopped") |
| 608 | |
| 609 | case "status": |
| 610 | if watchIsRunning(absRoot) { |
| 611 | state := watch.ReadState(absRoot) |
| 612 | if state != nil { |
| 613 | fmt.Printf("Watch daemon running\n") |
| 614 | fmt.Printf(" Files: %d\n", state.FileCount) |
| 615 | fmt.Printf(" Hubs: %d\n", len(state.Hubs)) |
| 616 | fmt.Printf(" Updated: %s\n", state.UpdatedAt.Format("15:04:05")) |