Skip to content

Commit 9f79a45

Browse files
author
Andrei
committed
initial
0 parents  commit 9f79a45

File tree

16 files changed

+744
-0
lines changed

16 files changed

+744
-0
lines changed

cmd/autostart.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
9+
"github.com/spf13/cobra"
10+
"github.com/itzcodex24/git-guardian/internal/supervisor"
11+
)
12+
13+
var autostartCmd = &cobra.Command{
14+
Use: "autostart",
15+
Short: "Manage launch-at-login (launchd) autostart for guardian",
16+
}
17+
18+
var autostartEnableCmd = &cobra.Command{
19+
Use: "enable",
20+
Short: "Enable autostart (install launch agent and load it)",
21+
RunE: func(cmd *cobra.Command, args []string) error {
22+
plist := supervisor.GenerateLaunchdPlist()
23+
home, _ := os.UserHomeDir()
24+
plistDir := filepath.Join(home, "Library", "LaunchAgents")
25+
if err := os.MkdirAll(plistDir, 0755); err != nil {
26+
return fmt.Errorf("failed to make launchagents dir: %w", err)
27+
}
28+
plistPath := filepath.Join(plistDir, "com.gitguardian.agent.plist")
29+
if err := os.WriteFile(plistPath, []byte(plist), 0644); err != nil {
30+
return fmt.Errorf("failed to write plist: %w", err)
31+
}
32+
exec.Command("launchctl", "load", plistPath).Run()
33+
fmt.Println("Autostart enabled. Plist:", plistPath)
34+
return nil
35+
},
36+
}
37+
38+
var autostartDisableCmd = &cobra.Command{
39+
Use: "disable",
40+
Short: "Disable autostart (unload and remove launch agent)",
41+
RunE: func(cmd *cobra.Command, args []string) error {
42+
home, _ := os.UserHomeDir()
43+
plistPath := filepath.Join(home, "Library", "LaunchAgents", "com.gitguardian.agent.plist")
44+
exec.Command("launchctl", "unload", plistPath).Run()
45+
os.Remove(plistPath)
46+
fmt.Println("Autostart disabled. Removed:", plistPath)
47+
return nil
48+
},
49+
}
50+
51+
func init() {
52+
autostartCmd.AddCommand(autostartEnableCmd)
53+
autostartCmd.AddCommand(autostartDisableCmd)
54+
rootCmd.AddCommand(autostartCmd)
55+
}

cmd/daemon.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/itzcodex24/git-guardian/internal/supervisor"
6+
)
7+
8+
var daemonCmd = &cobra.Command{
9+
Use: "daemon",
10+
Hidden: true,
11+
Short: "Internal: start daemon (used by launchd)",
12+
RunE: func(cmd *cobra.Command, args []string) error {
13+
supervisor.StartAllBlocking()
14+
return nil
15+
},
16+
}
17+
18+
func init() {
19+
rootCmd.AddCommand(daemonCmd)
20+
}

cmd/init.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package cmd
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"strings"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
var initCmd = &cobra.Command{
14+
Use: "init",
15+
Short: "Create a new GitHub repository using the GitHub CLI (`gh`) and initialize locally",
16+
RunE: func(cmd *cobra.Command, args []string) error {
17+
rd := bufio.NewReader(os.Stdin)
18+
19+
fmt.Print("Repository name (user/repo or repo): ")
20+
name, _ := rd.ReadString('\n')
21+
name = strings.TrimSpace(name)
22+
23+
fmt.Print("Private? (y/N): ")
24+
privRaw, _ := rd.ReadString('\n')
25+
priv := strings.ToLower(strings.TrimSpace(privRaw)) == "y"
26+
27+
flag := "--public"
28+
if priv {
29+
flag = "--private"
30+
}
31+
32+
create := exec.Command("gh", "repo", "create", name, flag, "--confirm")
33+
create.Stdout = os.Stdout
34+
create.Stderr = os.Stderr
35+
if err := create.Run(); err != nil {
36+
return fmt.Errorf("failed to create repo via gh: %w", err)
37+
}
38+
39+
fmt.Println("Created GitHub repo:", name)
40+
return nil
41+
},
42+
}
43+
44+
func init() {
45+
rootCmd.AddCommand(initCmd)
46+
}

cmd/link.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/google/uuid"
8+
"github.com/spf13/cobra"
9+
"github.com/itzcodex24/git-guardian/internal/state"
10+
)
11+
12+
var linkCmd = &cobra.Command{
13+
Use: "link <folder>",
14+
Args: cobra.ExactArgs(1),
15+
Short: "Link an existing folder to guardian (adds it to persistent state, initially paused)",
16+
RunE: func(cmd *cobra.Command, args []string) error {
17+
folder := args[0]
18+
if _, err := os.Stat(folder); os.IsNotExist(err) {
19+
return fmt.Errorf("folder does not exist: %s", folder)
20+
}
21+
22+
w := state.WatcherState{
23+
ID: uuid.NewString()[:8],
24+
Folder: folder,
25+
Mode: "none",
26+
Paused: true,
27+
}
28+
state.Append(w)
29+
fmt.Println("Linked folder (paused):", folder, "id:", w.ID)
30+
fmt.Println("Run `guardian start <folder> --watch` or `--interval <duration>` to enable.")
31+
return nil
32+
},
33+
}
34+
35+
func init() {
36+
rootCmd.AddCommand(linkCmd)
37+
}

cmd/listeners.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/spf13/cobra"
8+
"github.com/itzcodex24/git-guardian/internal/supervisor"
9+
)
10+
11+
var listenersCmd = &cobra.Command{
12+
Use: "listeners",
13+
Short: "List all configured watchers",
14+
RunE: func(cmd *cobra.Command, args []string) error {
15+
list := supervisor.List()
16+
fmt.Printf("\n%-8s %-10s %-45s %-10s %-25s\n", "ID", "Mode", "Folder", "Status", "LastRun")
17+
fmt.Println(strings.Repeat("-", 110))
18+
for _, w := range list {
19+
status := "paused"
20+
if !w.Paused {
21+
status = "running"
22+
}
23+
fmt.Printf("%-8s %-10s %-45s %-10s %-25s\n", w.ID, w.Mode, w.Folder, status, w.LastRun)
24+
}
25+
return nil
26+
},
27+
}
28+
29+
func init() {
30+
rootCmd.AddCommand(listenersCmd)
31+
}

cmd/pause.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
"github.com/itzcodex24/git-guardian/internal/supervisor"
8+
)
9+
10+
var pauseCmd = &cobra.Command{
11+
Use: "pause <id>",
12+
Args: cobra.ExactArgs(1),
13+
Short: "Pause a running watcher by id",
14+
RunE: func(cmd *cobra.Command, args []string) error {
15+
id := args[0]
16+
if err := supervisor.Pause(id); err != nil {
17+
return fmt.Errorf("pause failed: %w", err)
18+
}
19+
fmt.Println("Paused:", id)
20+
return nil
21+
},
22+
}
23+
24+
func init() {
25+
rootCmd.AddCommand(pauseCmd)
26+
}

cmd/remove.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
"github.com/itzcodex24/git-guardian/internal/supervisor"
8+
)
9+
10+
var removeCmd = &cobra.Command{
11+
Use: "remove <id>",
12+
Args: cobra.ExactArgs(1),
13+
Short: "Remove a watcher configuration and stop it",
14+
RunE: func(cmd *cobra.Command, args []string) error {
15+
id := args[0]
16+
if err := supervisor.Remove(id); err != nil {
17+
return fmt.Errorf("remove failed: %w", err)
18+
}
19+
fmt.Println("Removed:", id)
20+
return nil
21+
},
22+
}
23+
24+
func init() {
25+
rootCmd.AddCommand(removeCmd)
26+
}

cmd/resume.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
"github.com/itzcodex24/git-guardian/internal/supervisor"
8+
)
9+
10+
var resumeCmd = &cobra.Command{
11+
Use: "resume <id>",
12+
Args: cobra.ExactArgs(1),
13+
Short: "Resume a paused watcher by id",
14+
RunE: func(cmd *cobra.Command, args []string) error {
15+
id := args[0]
16+
if err := supervisor.Resume(id); err != nil {
17+
return fmt.Errorf("resume failed: %w", err)
18+
}
19+
fmt.Println("Resumed:", id)
20+
return nil
21+
},
22+
}
23+
24+
func init() {
25+
rootCmd.AddCommand(resumeCmd)
26+
}

cmd/root.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/itzcodex24/git-guardian/internal/supervisor"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
var rootCmd = &cobra.Command{
12+
Use: "guardian",
13+
Short: "Git Guardian — automatic git-backed folder watcher for macOS",
14+
Long: "Guardian watches folders, auto-commits and pushes changes to git/GitHub and runs as a macOS background agent.",
15+
}
16+
17+
func Execute() {
18+
// Load and start any configured watchers (fast startup).
19+
supervisor.StartAll()
20+
21+
if err := rootCmd.Execute(); err != nil {
22+
fmt.Println(err)
23+
os.Exit(1)
24+
}
25+
}

cmd/start.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
"github.com/itzcodex24/git-guardian/internal/state"
8+
)
9+
10+
var (
11+
startInterval string
12+
startDebounce string
13+
startWatch bool
14+
)
15+
16+
var startCmd = &cobra.Command{
17+
Use: "start <folder>",
18+
Args: cobra.ExactArgs(1),
19+
Short: "Start a watcher for a linked folder (watch mode or interval mode)",
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
folder := args[0]
22+
list := state.Get()
23+
for i := range list {
24+
if list[i].Folder == folder {
25+
if startWatch {
26+
list[i].Mode = "watch"
27+
list[i].Debounce = startDebounce
28+
} else if startInterval != "" {
29+
list[i].Mode = "interval"
30+
list[i].Interval = startInterval
31+
} else {
32+
return fmt.Errorf("must specify --watch or --interval <duration>")
33+
}
34+
list[i].Paused = false
35+
state.Update(list)
36+
fmt.Println("Watcher activated for:", folder)
37+
return nil
38+
}
39+
}
40+
return fmt.Errorf("folder not linked: %s. Use `guardian link` first", folder)
41+
},
42+
}
43+
44+
func init() {
45+
startCmd.Flags().BoolVar(&startWatch, "watch", false, "Use file-change watch mode")
46+
startCmd.Flags().StringVar(&startInterval, "interval", "", "Use interval mode, e.g. 5m")
47+
startCmd.Flags().StringVar(&startDebounce, "debounce", "30s", "Debounce duration for watch mode")
48+
rootCmd.AddCommand(startCmd)
49+
}

0 commit comments

Comments
 (0)