refactor(cli): rename target(s) to remote(s) for clarity
- commit
- 349ccfa848c0f3e06e6b390f44c2c6567446aa6a
- author
- bnema · · 2026-01-16 20:57 UTC
- parent
- 4153967
- signature
- unsigned
6 files · +352 -352
M docs/cli/commands.md +24 -24
| @@ -14,7 +14,7 @@ Gordon provides a command-line interface for server management, deployment, and | |||
| 14 | 14 | | `gordon auth` | Manage registry authentication | | |
| 15 | 15 | | `gordon routes` | Manage routes (local or remote) | | |
| 16 | 16 | | `gordon secrets` | Manage secrets (remote only) | | |
| 17 | - | | `gordon targets` | Manage remote Gordon targets | | |
| 17 | + | | `gordon remotes` | Manage saved remote Gordon instances | | |
| 18 | 18 | ||
| 19 | 19 | ## Quick Reference | |
| 20 | 20 | ||
| @@ -50,19 +50,19 @@ gordon auth internal | |||
| 50 | 50 | | Option | Description | | |
| 51 | 51 | |--------|-------------| | |
| 52 | 52 | | `-c, --config` | Path to configuration file | | |
| 53 | - | | `--target` | Remote Gordon URL (e.g., `https://gordon.mydomain.com`) | | |
| 54 | - | | `--token` | Authentication token for remote target | | |
| 53 | + | | `--remote` | Remote Gordon URL (e.g., `https://gordon.mydomain.com`) | | |
| 54 | + | | `--token` | Authentication token for remote | | |
| 55 | 55 | ||
| 56 | 56 | ### Remote Targeting | |
| 57 | 57 | ||
| 58 | - | The CLI can target remote Gordon instances using the `--target` flag or `GORDON_TARGET` environment variable: | |
| 58 | + | The CLI can target remote Gordon instances using the `--remote` flag or `GORDON_REMOTE` environment variable: | |
| 59 | 59 | ||
| 60 | 60 | ```bash | |
| 61 | 61 | # Using flags | |
| 62 | - | gordon routes list --target https://gordon.mydomain.com --token $TOKEN | |
| 62 | + | gordon routes list --remote https://gordon.mydomain.com --token $TOKEN | |
| 63 | 63 | ||
| 64 | 64 | # Using environment variables | |
| 65 | - | export GORDON_TARGET=https://gordon.mydomain.com | |
| 65 | + | export GORDON_REMOTE=https://gordon.mydomain.com | |
| 66 | 66 | export GORDON_TOKEN=$TOKEN | |
| 67 | 67 | gordon routes list | |
| 68 | 68 | ``` | |
| @@ -567,7 +567,7 @@ List all configured routes. | |||
| 567 | 567 | ||
| 568 | 568 | ```bash | |
| 569 | 569 | gordon routes list | |
| 570 | - | gordon routes list --target https://gordon.mydomain.com --token $TOKEN | |
| 570 | + | gordon routes list --remote https://gordon.mydomain.com --token $TOKEN | |
| 571 | 571 | ``` | |
| 572 | 572 | ||
| 573 | 573 | ### gordon routes add | |
| @@ -616,46 +616,46 @@ Manage secrets on remote Gordon instances. Requires remote targeting. | |||
| 616 | 616 | ||
| 617 | 617 | ```bash | |
| 618 | 618 | # List secrets | |
| 619 | - | gordon secrets list --target https://gordon.mydomain.com --token $TOKEN | |
| 619 | + | gordon secrets list --remote https://gordon.mydomain.com --token $TOKEN | |
| 620 | 620 | ||
| 621 | 621 | # Set a secret | |
| 622 | - | gordon secrets set DATABASE_URL "postgres://..." --target ... --token ... | |
| 622 | + | gordon secrets set DATABASE_URL "postgres://..." --remote ... --token ... | |
| 623 | 623 | ||
| 624 | 624 | # Get a secret | |
| 625 | - | gordon secrets get DATABASE_URL --target ... --token ... | |
| 625 | + | gordon secrets get DATABASE_URL --remote ... --token ... | |
| 626 | 626 | ||
| 627 | 627 | # Delete a secret | |
| 628 | - | gordon secrets delete DATABASE_URL --target ... --token ... | |
| 628 | + | gordon secrets delete DATABASE_URL --remote ... --token ... | |
| 629 | 629 | ``` | |
| 630 | 630 | ||
| 631 | 631 | --- | |
| 632 | 632 | ||
| 633 | - | ## gordon targets | |
| 633 | + | ## gordon remotes | |
| 634 | 634 | ||
| 635 | - | Manage saved remote Gordon targets for easier CLI usage. | |
| 635 | + | Manage saved remote Gordon instances for easier CLI usage. | |
| 636 | 636 | ||
| 637 | 637 | ### Subcommands | |
| 638 | 638 | ||
| 639 | 639 | | Subcommand | Description | | |
| 640 | 640 | |------------|-------------| | |
| 641 | - | | `list` | List saved targets | | |
| 642 | - | | `add` | Add a new target | | |
| 643 | - | | `remove` | Remove a saved target | | |
| 644 | - | | `set-default` | Set default target | | |
| 641 | + | | `list` | List saved remotes | | |
| 642 | + | | `add` | Add a new remote | | |
| 643 | + | | `remove` | Remove a saved remote | | |
| 644 | + | | `use` | Set active remote | | |
| 645 | 645 | ||
| 646 | 646 | ### Examples | |
| 647 | 647 | ||
| 648 | 648 | ```bash | |
| 649 | - | # Add a target | |
| 650 | - | gordon targets add prod https://gordon.mydomain.com $TOKEN | |
| 649 | + | # Add a remote | |
| 650 | + | gordon remotes add prod https://gordon.mydomain.com --token $TOKEN | |
| 651 | 651 | ||
| 652 | - | # List saved targets | |
| 653 | - | gordon targets list | |
| 652 | + | # List saved remotes | |
| 653 | + | gordon remotes list | |
| 654 | 654 | ||
| 655 | - | # Set default target | |
| 656 | - | gordon targets set-default prod | |
| 655 | + | # Set active remote | |
| 656 | + | gordon remotes use prod | |
| 657 | 657 | ||
| 658 | - | # Now use without --target flag | |
| 658 | + | # Now use without --remote flag | |
| 659 | 659 | gordon routes list | |
| 660 | 660 | ``` | |
| 661 | 661 | ||
| @@ -14,7 +14,7 @@ Gordon provides a command-line interface for server management, deployment, and | |||||
| 14 | | `gordon auth` | Manage registry authentication | | 14 | | `gordon auth` | Manage registry authentication | | ||
| 15 | | `gordon routes` | Manage routes (local or remote) | | 15 | | `gordon routes` | Manage routes (local or remote) | | ||
| 16 | | `gordon secrets` | Manage secrets (remote only) | | 16 | | `gordon secrets` | Manage secrets (remote only) | | ||
| 17 | - | | `gordon targets` | Manage remote Gordon targets | | 17 | + | | `gordon remotes` | Manage saved remote Gordon instances | |
| 18 | 18 | ||||
| 19 | ## Quick Reference | 19 | ## Quick Reference | ||
| 20 | 20 | ||||
| @@ -50,19 +50,19 @@ gordon auth internal | |||||
| 50 | | Option | Description | | 50 | | Option | Description | | ||
| 51 | |--------|-------------| | 51 | |--------|-------------| | ||
| 52 | | `-c, --config` | Path to configuration file | | 52 | | `-c, --config` | Path to configuration file | | ||
| 53 | - | | `--target` | Remote Gordon URL (e.g., `https://gordon.mydomain.com`) | | 53 | + | | `--remote` | Remote Gordon URL (e.g., `https://gordon.mydomain.com`) | |
| 54 | - | | `--token` | Authentication token for remote target | | 54 | + | | `--token` | Authentication token for remote | |
| 55 | 55 | ||||
| 56 | ### Remote Targeting | 56 | ### Remote Targeting | ||
| 57 | 57 | ||||
| 58 | - | The CLI can target remote Gordon instances using the `--target` flag or `GORDON_TARGET` environment variable: | 58 | + | The CLI can target remote Gordon instances using the `--remote` flag or `GORDON_REMOTE` environment variable: |
| 59 | 59 | ||||
| 60 | ```bash | 60 | ```bash | ||
| 61 | # Using flags | 61 | # Using flags | ||
| 62 | - | gordon routes list --target https://gordon.mydomain.com --token $TOKEN | 62 | + | gordon routes list --remote https://gordon.mydomain.com --token $TOKEN |
| 63 | 63 | ||||
| 64 | # Using environment variables | 64 | # Using environment variables | ||
| 65 | - | export GORDON_TARGET=https://gordon.mydomain.com | 65 | + | export GORDON_REMOTE=https://gordon.mydomain.com |
| 66 | export GORDON_TOKEN=$TOKEN | 66 | export GORDON_TOKEN=$TOKEN | ||
| 67 | gordon routes list | 67 | gordon routes list | ||
| 68 | ``` | 68 | ``` | ||
| @@ -567,7 +567,7 @@ List all configured routes. | |||||
| 567 | 567 | ||||
| 568 | ```bash | 568 | ```bash | ||
| 569 | gordon routes list | 569 | gordon routes list | ||
| 570 | - | gordon routes list --target https://gordon.mydomain.com --token $TOKEN | 570 | + | gordon routes list --remote https://gordon.mydomain.com --token $TOKEN |
| 571 | ``` | 571 | ``` | ||
| 572 | 572 | ||||
| 573 | ### gordon routes add | 573 | ### gordon routes add | ||
| @@ -616,46 +616,46 @@ Manage secrets on remote Gordon instances. Requires remote targeting. | |||||
| 616 | 616 | ||||
| 617 | ```bash | 617 | ```bash | ||
| 618 | # List secrets | 618 | # List secrets | ||
| 619 | - | gordon secrets list --target https://gordon.mydomain.com --token $TOKEN | 619 | + | gordon secrets list --remote https://gordon.mydomain.com --token $TOKEN |
| 620 | 620 | ||||
| 621 | # Set a secret | 621 | # Set a secret | ||
| 622 | - | gordon secrets set DATABASE_URL "postgres://..." --target ... --token ... | 622 | + | gordon secrets set DATABASE_URL "postgres://..." --remote ... --token ... |
| 623 | 623 | ||||
| 624 | # Get a secret | 624 | # Get a secret | ||
| 625 | - | gordon secrets get DATABASE_URL --target ... --token ... | 625 | + | gordon secrets get DATABASE_URL --remote ... --token ... |
| 626 | 626 | ||||
| 627 | # Delete a secret | 627 | # Delete a secret | ||
| 628 | - | gordon secrets delete DATABASE_URL --target ... --token ... | 628 | + | gordon secrets delete DATABASE_URL --remote ... --token ... |
| 629 | ``` | 629 | ``` | ||
| 630 | 630 | ||||
| 631 | --- | 631 | --- | ||
| 632 | 632 | ||||
| 633 | - | ## gordon targets | 633 | + | ## gordon remotes |
| 634 | 634 | ||||
| 635 | - | Manage saved remote Gordon targets for easier CLI usage. | 635 | + | Manage saved remote Gordon instances for easier CLI usage. |
| 636 | 636 | ||||
| 637 | ### Subcommands | 637 | ### Subcommands | ||
| 638 | 638 | ||||
| 639 | | Subcommand | Description | | 639 | | Subcommand | Description | | ||
| 640 | |------------|-------------| | 640 | |------------|-------------| | ||
| 641 | - | | `list` | List saved targets | | 641 | + | | `list` | List saved remotes | |
| 642 | - | | `add` | Add a new target | | 642 | + | | `add` | Add a new remote | |
| 643 | - | | `remove` | Remove a saved target | | 643 | + | | `remove` | Remove a saved remote | |
| 644 | - | | `set-default` | Set default target | | 644 | + | | `use` | Set active remote | |
| 645 | 645 | ||||
| 646 | ### Examples | 646 | ### Examples | ||
| 647 | 647 | ||||
| 648 | ```bash | 648 | ```bash | ||
| 649 | - | # Add a target | 649 | + | # Add a remote |
| 650 | - | gordon targets add prod https://gordon.mydomain.com $TOKEN | 650 | + | gordon remotes add prod https://gordon.mydomain.com --token $TOKEN |
| 651 | 651 | ||||
| 652 | - | # List saved targets | 652 | + | # List saved remotes |
| 653 | - | gordon targets list | 653 | + | gordon remotes list |
| 654 | 654 | ||||
| 655 | - | # Set default target | 655 | + | # Set active remote |
| 656 | - | gordon targets set-default prod | 656 | + | gordon remotes use prod |
| 657 | 657 | ||||
| 658 | - | # Now use without --target flag | 658 | + | # Now use without --remote flag |
| 659 | gordon routes list | 659 | gordon routes list | ||
| 660 | ``` | 660 | ``` | ||
| 661 | 661 | ||||
M docs/config/auto-route.md +2 -2
| @@ -249,12 +249,12 @@ Gordon will: | |||
| 249 | 249 | ### Example: Complete Setup | |
| 250 | 250 | ||
| 251 | 251 | ```dockerfile | |
| 252 | - | FROM golang:1.21-alpine AS builder | |
| 252 | + | FROM golang:1.25-alpine AS builder | |
| 253 | 253 | WORKDIR /app | |
| 254 | 254 | COPY . . | |
| 255 | 255 | RUN go build -o server | |
| 256 | 256 | ||
| 257 | - | FROM alpine:3.19 | |
| 257 | + | FROM alpine:3.21 | |
| 258 | 258 | WORKDIR /app | |
| 259 | 259 | COPY --from=builder /app/server . | |
| 260 | 260 | COPY .env.example . | |
| @@ -249,12 +249,12 @@ Gordon will: | |||||
| 249 | ### Example: Complete Setup | 249 | ### Example: Complete Setup | ||
| 250 | 250 | ||||
| 251 | ```dockerfile | 251 | ```dockerfile | ||
| 252 | - | FROM golang:1.21-alpine AS builder | 252 | + | FROM golang:1.25-alpine AS builder |
| 253 | WORKDIR /app | 253 | WORKDIR /app | ||
| 254 | COPY . . | 254 | COPY . . | ||
| 255 | RUN go build -o server | 255 | RUN go build -o server | ||
| 256 | 256 | ||||
| 257 | - | FROM alpine:3.19 | 257 | + | FROM alpine:3.21 |
| 258 | WORKDIR /app | 258 | WORKDIR /app | ||
| 259 | COPY --from=builder /app/server . | 259 | COPY --from=builder /app/server . | ||
| 260 | COPY .env.example . | 260 | COPY .env.example . | ||
M internal/adapters/in/cli/remote/config.go +72 -72
| @@ -11,20 +11,20 @@ import ( | |||
| 11 | 11 | // ClientConfig represents the client-mode configuration. | |
| 12 | 12 | type ClientConfig struct { | |
| 13 | 13 | Client ClientSettings `toml:"client"` | |
| 14 | - | Targets map[string]TargetEntry `toml:"targets"` | |
| 15 | - | Active string `toml:"active"` // Active target name | |
| 14 | + | Remotes map[string]RemoteEntry `toml:"remotes"` | |
| 15 | + | Active string `toml:"active"` // Active remote name | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | 18 | // ClientSettings represents the [client] section. | |
| 19 | 19 | type ClientSettings struct { | |
| 20 | 20 | Mode string `toml:"mode"` // "local" (default) or "remote" | |
| 21 | - | Target string `toml:"target"` // Remote Gordon URL | |
| 21 | + | Remote string `toml:"remote"` // Remote Gordon URL | |
| 22 | 22 | Token string `toml:"token"` // Auth token | |
| 23 | 23 | TokenEnv string `toml:"token_env"` // Env var name for token | |
| 24 | 24 | } | |
| 25 | 25 | ||
| 26 | - | // TargetEntry represents a saved target in [targets.*]. | |
| 27 | - | type TargetEntry struct { | |
| 26 | + | // RemoteEntry represents a saved remote in [remotes.*]. | |
| 27 | + | type RemoteEntry struct { | |
| 28 | 28 | URL string `toml:"url"` | |
| 29 | 29 | Token string `toml:"token,omitempty"` | |
| 30 | 30 | TokenEnv string `toml:"token_env,omitempty"` | |
| @@ -39,13 +39,13 @@ func DefaultClientConfigPath() string { | |||
| 39 | 39 | return filepath.Join(configDir, "gordon", "gordon.toml") | |
| 40 | 40 | } | |
| 41 | 41 | ||
| 42 | - | // DefaultTargetsPath returns the default targets config path. | |
| 43 | - | func DefaultTargetsPath() string { | |
| 42 | + | // DefaultRemotesPath returns the default remotes config path. | |
| 43 | + | func DefaultRemotesPath() string { | |
| 44 | 44 | configDir, err := os.UserConfigDir() | |
| 45 | 45 | if err != nil { | |
| 46 | 46 | configDir = os.Getenv("HOME") | |
| 47 | 47 | } | |
| 48 | - | return filepath.Join(configDir, "gordon", "targets.toml") | |
| 48 | + | return filepath.Join(configDir, "gordon", "remotes.toml") | |
| 49 | 49 | } | |
| 50 | 50 | ||
| 51 | 51 | // LoadClientConfig loads the client configuration. | |
| @@ -62,7 +62,7 @@ func LoadClientConfig(path string) (*ClientConfig, error) { | |||
| 62 | 62 | Client: ClientSettings{ | |
| 63 | 63 | Mode: "local", | |
| 64 | 64 | }, | |
| 65 | - | Targets: make(map[string]TargetEntry), | |
| 65 | + | Remotes: make(map[string]RemoteEntry), | |
| 66 | 66 | }, nil | |
| 67 | 67 | } | |
| 68 | 68 | return nil, fmt.Errorf("failed to read config: %w", err) | |
| @@ -73,45 +73,45 @@ func LoadClientConfig(path string) (*ClientConfig, error) { | |||
| 73 | 73 | return nil, fmt.Errorf("failed to parse config: %w", err) | |
| 74 | 74 | } | |
| 75 | 75 | ||
| 76 | - | if config.Targets == nil { | |
| 77 | - | config.Targets = make(map[string]TargetEntry) | |
| 76 | + | if config.Remotes == nil { | |
| 77 | + | config.Remotes = make(map[string]RemoteEntry) | |
| 78 | 78 | } | |
| 79 | 79 | ||
| 80 | 80 | return &config, nil | |
| 81 | 81 | } | |
| 82 | 82 | ||
| 83 | - | // LoadTargets loads the targets configuration. | |
| 84 | - | func LoadTargets(path string) (*ClientConfig, error) { | |
| 83 | + | // LoadRemotes loads the remotes configuration. | |
| 84 | + | func LoadRemotes(path string) (*ClientConfig, error) { | |
| 85 | 85 | if path == "" { | |
| 86 | - | path = DefaultTargetsPath() | |
| 86 | + | path = DefaultRemotesPath() | |
| 87 | 87 | } | |
| 88 | 88 | ||
| 89 | 89 | data, err := os.ReadFile(path) | |
| 90 | 90 | if err != nil { | |
| 91 | 91 | if os.IsNotExist(err) { | |
| 92 | 92 | return &ClientConfig{ | |
| 93 | - | Targets: make(map[string]TargetEntry), | |
| 93 | + | Remotes: make(map[string]RemoteEntry), | |
| 94 | 94 | }, nil | |
| 95 | 95 | } | |
| 96 | - | return nil, fmt.Errorf("failed to read targets: %w", err) | |
| 96 | + | return nil, fmt.Errorf("failed to read remotes: %w", err) | |
| 97 | 97 | } | |
| 98 | 98 | ||
| 99 | 99 | var config ClientConfig | |
| 100 | 100 | if err := toml.Unmarshal(data, &config); err != nil { | |
| 101 | - | return nil, fmt.Errorf("failed to parse targets: %w", err) | |
| 101 | + | return nil, fmt.Errorf("failed to parse remotes: %w", err) | |
| 102 | 102 | } | |
| 103 | 103 | ||
| 104 | - | if config.Targets == nil { | |
| 105 | - | config.Targets = make(map[string]TargetEntry) | |
| 104 | + | if config.Remotes == nil { | |
| 105 | + | config.Remotes = make(map[string]RemoteEntry) | |
| 106 | 106 | } | |
| 107 | 107 | ||
| 108 | 108 | return &config, nil | |
| 109 | 109 | } | |
| 110 | 110 | ||
| 111 | - | // SaveTargets saves the targets configuration. | |
| 112 | - | func SaveTargets(path string, config *ClientConfig) error { | |
| 111 | + | // SaveRemotes saves the remotes configuration. | |
| 112 | + | func SaveRemotes(path string, config *ClientConfig) error { | |
| 113 | 113 | if path == "" { | |
| 114 | - | path = DefaultTargetsPath() | |
| 114 | + | path = DefaultRemotesPath() | |
| 115 | 115 | } | |
| 116 | 116 | ||
| 117 | 117 | // Ensure directory exists | |
| @@ -122,51 +122,51 @@ func SaveTargets(path string, config *ClientConfig) error { | |||
| 122 | 122 | ||
| 123 | 123 | data, err := toml.Marshal(config) | |
| 124 | 124 | if err != nil { | |
| 125 | - | return fmt.Errorf("failed to marshal targets: %w", err) | |
| 125 | + | return fmt.Errorf("failed to marshal remotes: %w", err) | |
| 126 | 126 | } | |
| 127 | 127 | ||
| 128 | 128 | if err := os.WriteFile(path, data, 0600); err != nil { | |
| 129 | - | return fmt.Errorf("failed to write targets: %w", err) | |
| 129 | + | return fmt.Errorf("failed to write remotes: %w", err) | |
| 130 | 130 | } | |
| 131 | 131 | ||
| 132 | 132 | return nil | |
| 133 | 133 | } | |
| 134 | 134 | ||
| 135 | - | // ResolveTarget resolves the target URL and token from configuration. | |
| 136 | - | // Precedence: flag > env > config > active target | |
| 137 | - | func ResolveTarget(flagTarget, flagToken string) (url, token string, isRemote bool) { | |
| 135 | + | // ResolveRemote resolves the remote URL and token from configuration. | |
| 136 | + | // Precedence: flag > env > config > active remote | |
| 137 | + | func ResolveRemote(flagRemote, flagToken string) (url, token string, isRemote bool) { | |
| 138 | 138 | config, _ := LoadClientConfig("") | |
| 139 | - | targets, _ := LoadTargets("") | |
| 139 | + | remotes, _ := LoadRemotes("") | |
| 140 | 140 | ||
| 141 | - | url, isRemote = resolveTargetURL(flagTarget, config, targets) | |
| 141 | + | url, isRemote = resolveRemoteURL(flagRemote, config, remotes) | |
| 142 | 142 | if isRemote { | |
| 143 | - | token = resolveToken(flagToken, config, targets) | |
| 143 | + | token = resolveToken(flagToken, config, remotes) | |
| 144 | 144 | } | |
| 145 | 145 | ||
| 146 | 146 | return url, token, isRemote | |
| 147 | 147 | } | |
| 148 | 148 | ||
| 149 | - | // resolveTargetURL resolves the target URL from various sources. | |
| 150 | - | func resolveTargetURL(flagTarget string, config *ClientConfig, targets *ClientConfig) (string, bool) { | |
| 149 | + | // resolveRemoteURL resolves the remote URL from various sources. | |
| 150 | + | func resolveRemoteURL(flagRemote string, config *ClientConfig, remotes *ClientConfig) (string, bool) { | |
| 151 | 151 | // 1. Check flag | |
| 152 | - | if flagTarget != "" { | |
| 153 | - | return flagTarget, true | |
| 152 | + | if flagRemote != "" { | |
| 153 | + | return flagRemote, true | |
| 154 | 154 | } | |
| 155 | 155 | ||
| 156 | 156 | // 2. Check environment variable | |
| 157 | - | if envTarget := os.Getenv("GORDON_TARGET"); envTarget != "" { | |
| 158 | - | return envTarget, true | |
| 157 | + | if envRemote := os.Getenv("GORDON_REMOTE"); envRemote != "" { | |
| 158 | + | return envRemote, true | |
| 159 | 159 | } | |
| 160 | 160 | ||
| 161 | 161 | // 3. Check client config | |
| 162 | - | if config != nil && config.Client.Mode == "remote" && config.Client.Target != "" { | |
| 163 | - | return config.Client.Target, true | |
| 162 | + | if config != nil && config.Client.Mode == "remote" && config.Client.Remote != "" { | |
| 163 | + | return config.Client.Remote, true | |
| 164 | 164 | } | |
| 165 | 165 | ||
| 166 | - | // 4. Check active target | |
| 167 | - | if targets != nil && targets.Active != "" { | |
| 168 | - | if target, ok := targets.Targets[targets.Active]; ok { | |
| 169 | - | return target.URL, true | |
| 166 | + | // 4. Check active remote | |
| 167 | + | if remotes != nil && remotes.Active != "" { | |
| 168 | + | if remote, ok := remotes.Remotes[remotes.Active]; ok { | |
| 169 | + | return remote.URL, true | |
| 170 | 170 | } | |
| 171 | 171 | } | |
| 172 | 172 | ||
| @@ -174,7 +174,7 @@ func resolveTargetURL(flagTarget string, config *ClientConfig, targets *ClientCo | |||
| 174 | 174 | } | |
| 175 | 175 | ||
| 176 | 176 | // resolveToken resolves the authentication token from various sources. | |
| 177 | - | func resolveToken(flagToken string, config *ClientConfig, targets *ClientConfig) string { | |
| 177 | + | func resolveToken(flagToken string, config *ClientConfig, remotes *ClientConfig) string { | |
| 178 | 178 | // 1. Flag token takes precedence | |
| 179 | 179 | if flagToken != "" { | |
| 180 | 180 | return flagToken | |
| @@ -195,14 +195,14 @@ func resolveToken(flagToken string, config *ClientConfig, targets *ClientConfig) | |||
| 195 | 195 | } | |
| 196 | 196 | } | |
| 197 | 197 | ||
| 198 | - | // 4. Active target token | |
| 199 | - | if targets != nil && targets.Active != "" { | |
| 200 | - | if target, ok := targets.Targets[targets.Active]; ok { | |
| 201 | - | if target.Token != "" { | |
| 202 | - | return target.Token | |
| 198 | + | // 4. Active remote token | |
| 199 | + | if remotes != nil && remotes.Active != "" { | |
| 200 | + | if remote, ok := remotes.Remotes[remotes.Active]; ok { | |
| 201 | + | if remote.Token != "" { | |
| 202 | + | return remote.Token | |
| 203 | 203 | } | |
| 204 | - | if target.TokenEnv != "" { | |
| 205 | - | return os.Getenv(target.TokenEnv) | |
| 204 | + | if remote.TokenEnv != "" { | |
| 205 | + | return os.Getenv(remote.TokenEnv) | |
| 206 | 206 | } | |
| 207 | 207 | } | |
| 208 | 208 | } | |
| @@ -210,61 +210,61 @@ func resolveToken(flagToken string, config *ClientConfig, targets *ClientConfig) | |||
| 210 | 210 | return "" | |
| 211 | 211 | } | |
| 212 | 212 | ||
| 213 | - | // AddTarget adds a new target to the targets configuration. | |
| 214 | - | func AddTarget(name, url, token string) error { | |
| 215 | - | config, err := LoadTargets("") | |
| 213 | + | // AddRemote adds a new remote to the remotes configuration. | |
| 214 | + | func AddRemote(name, url, token string) error { | |
| 215 | + | config, err := LoadRemotes("") | |
| 216 | 216 | if err != nil { | |
| 217 | 217 | return err | |
| 218 | 218 | } | |
| 219 | 219 | ||
| 220 | - | config.Targets[name] = TargetEntry{ | |
| 220 | + | config.Remotes[name] = RemoteEntry{ | |
| 221 | 221 | URL: url, | |
| 222 | 222 | Token: token, | |
| 223 | 223 | } | |
| 224 | 224 | ||
| 225 | - | return SaveTargets("", config) | |
| 225 | + | return SaveRemotes("", config) | |
| 226 | 226 | } | |
| 227 | 227 | ||
| 228 | - | // RemoveTarget removes a target from the targets configuration. | |
| 229 | - | func RemoveTarget(name string) error { | |
| 230 | - | config, err := LoadTargets("") | |
| 228 | + | // RemoveRemote removes a remote from the remotes configuration. | |
| 229 | + | func RemoveRemote(name string) error { | |
| 230 | + | config, err := LoadRemotes("") | |
| 231 | 231 | if err != nil { | |
| 232 | 232 | return err | |
| 233 | 233 | } | |
| 234 | 234 | ||
| 235 | - | delete(config.Targets, name) | |
| 235 | + | delete(config.Remotes, name) | |
| 236 | 236 | ||
| 237 | - | // Clear active if it was the removed target | |
| 237 | + | // Clear active if it was the removed remote | |
| 238 | 238 | if config.Active == name { | |
| 239 | 239 | config.Active = "" | |
| 240 | 240 | } | |
| 241 | 241 | ||
| 242 | - | return SaveTargets("", config) | |
| 242 | + | return SaveRemotes("", config) | |
| 243 | 243 | } | |
| 244 | 244 | ||
| 245 | - | // SetActiveTarget sets the active target. | |
| 246 | - | func SetActiveTarget(name string) error { | |
| 247 | - | config, err := LoadTargets("") | |
| 245 | + | // SetActiveRemote sets the active remote. | |
| 246 | + | func SetActiveRemote(name string) error { | |
| 247 | + | config, err := LoadRemotes("") | |
| 248 | 248 | if err != nil { | |
| 249 | 249 | return err | |
| 250 | 250 | } | |
| 251 | 251 | ||
| 252 | - | // Verify target exists | |
| 253 | - | if _, ok := config.Targets[name]; !ok { | |
| 254 | - | return fmt.Errorf("target '%s' not found", name) | |
| 252 | + | // Verify remote exists | |
| 253 | + | if _, ok := config.Remotes[name]; !ok { | |
| 254 | + | return fmt.Errorf("remote '%s' not found", name) | |
| 255 | 255 | } | |
| 256 | 256 | ||
| 257 | 257 | config.Active = name | |
| 258 | 258 | ||
| 259 | - | return SaveTargets("", config) | |
| 259 | + | return SaveRemotes("", config) | |
| 260 | 260 | } | |
| 261 | 261 | ||
| 262 | - | // ListTargets returns all saved targets. | |
| 263 | - | func ListTargets() (map[string]TargetEntry, string, error) { | |
| 264 | - | config, err := LoadTargets("") | |
| 262 | + | // ListRemotes returns all saved remotes. | |
| 263 | + | func ListRemotes() (map[string]RemoteEntry, string, error) { | |
| 264 | + | config, err := LoadRemotes("") | |
| 265 | 265 | if err != nil { | |
| 266 | 266 | return nil, "", err | |
| 267 | 267 | } | |
| 268 | 268 | ||
| 269 | - | return config.Targets, config.Active, nil | |
| 269 | + | return config.Remotes, config.Active, nil | |
| 270 | 270 | } | |
| @@ -11,20 +11,20 @@ import ( | |||||
| 11 | // ClientConfig represents the client-mode configuration. | 11 | // ClientConfig represents the client-mode configuration. | ||
| 12 | type ClientConfig struct { | 12 | type ClientConfig struct { | ||
| 13 | Client ClientSettings `toml:"client"` | 13 | Client ClientSettings `toml:"client"` | ||
| 14 | - | Targets map[string]TargetEntry `toml:"targets"` | 14 | + | Remotes map[string]RemoteEntry `toml:"remotes"` |
| 15 | - | Active string `toml:"active"` // Active target name | 15 | + | Active string `toml:"active"` // Active remote name |
| 16 | } | 16 | } | ||
| 17 | 17 | ||||
| 18 | // ClientSettings represents the [client] section. | 18 | // ClientSettings represents the [client] section. | ||
| 19 | type ClientSettings struct { | 19 | type ClientSettings struct { | ||
| 20 | Mode string `toml:"mode"` // "local" (default) or "remote" | 20 | Mode string `toml:"mode"` // "local" (default) or "remote" | ||
| 21 | - | Target string `toml:"target"` // Remote Gordon URL | 21 | + | Remote string `toml:"remote"` // Remote Gordon URL |
| 22 | Token string `toml:"token"` // Auth token | 22 | Token string `toml:"token"` // Auth token | ||
| 23 | TokenEnv string `toml:"token_env"` // Env var name for token | 23 | TokenEnv string `toml:"token_env"` // Env var name for token | ||
| 24 | } | 24 | } | ||
| 25 | 25 | ||||
| 26 | - | // TargetEntry represents a saved target in [targets.*]. | 26 | + | // RemoteEntry represents a saved remote in [remotes.*]. |
| 27 | - | type TargetEntry struct { | 27 | + | type RemoteEntry struct { |
| 28 | URL string `toml:"url"` | 28 | URL string `toml:"url"` | ||
| 29 | Token string `toml:"token,omitempty"` | 29 | Token string `toml:"token,omitempty"` | ||
| 30 | TokenEnv string `toml:"token_env,omitempty"` | 30 | TokenEnv string `toml:"token_env,omitempty"` | ||
| @@ -39,13 +39,13 @@ func DefaultClientConfigPath() string { | |||||
| 39 | return filepath.Join(configDir, "gordon", "gordon.toml") | 39 | return filepath.Join(configDir, "gordon", "gordon.toml") | ||
| 40 | } | 40 | } | ||
| 41 | 41 | ||||
| 42 | - | // DefaultTargetsPath returns the default targets config path. | 42 | + | // DefaultRemotesPath returns the default remotes config path. |
| 43 | - | func DefaultTargetsPath() string { | 43 | + | func DefaultRemotesPath() string { |
| 44 | configDir, err := os.UserConfigDir() | 44 | configDir, err := os.UserConfigDir() | ||
| 45 | if err != nil { | 45 | if err != nil { | ||
| 46 | configDir = os.Getenv("HOME") | 46 | configDir = os.Getenv("HOME") | ||
| 47 | } | 47 | } | ||
| 48 | - | return filepath.Join(configDir, "gordon", "targets.toml") | 48 | + | return filepath.Join(configDir, "gordon", "remotes.toml") |
| 49 | } | 49 | } | ||
| 50 | 50 | ||||
| 51 | // LoadClientConfig loads the client configuration. | 51 | // LoadClientConfig loads the client configuration. | ||
| @@ -62,7 +62,7 @@ func LoadClientConfig(path string) (*ClientConfig, error) { | |||||
| 62 | Client: ClientSettings{ | 62 | Client: ClientSettings{ | ||
| 63 | Mode: "local", | 63 | Mode: "local", | ||
| 64 | }, | 64 | }, | ||
| 65 | - | Targets: make(map[string]TargetEntry), | 65 | + | Remotes: make(map[string]RemoteEntry), |
| 66 | }, nil | 66 | }, nil | ||
| 67 | } | 67 | } | ||
| 68 | return nil, fmt.Errorf("failed to read config: %w", err) | 68 | return nil, fmt.Errorf("failed to read config: %w", err) | ||
| @@ -73,45 +73,45 @@ func LoadClientConfig(path string) (*ClientConfig, error) { | |||||
| 73 | return nil, fmt.Errorf("failed to parse config: %w", err) | 73 | return nil, fmt.Errorf("failed to parse config: %w", err) | ||
| 74 | } | 74 | } | ||
| 75 | 75 | ||||
| 76 | - | if config.Targets == nil { | 76 | + | if config.Remotes == nil { |
| 77 | - | config.Targets = make(map[string]TargetEntry) | 77 | + | config.Remotes = make(map[string]RemoteEntry) |
| 78 | } | 78 | } | ||
| 79 | 79 | ||||
| 80 | return &config, nil | 80 | return &config, nil | ||
| 81 | } | 81 | } | ||
| 82 | 82 | ||||
| 83 | - | // LoadTargets loads the targets configuration. | 83 | + | // LoadRemotes loads the remotes configuration. |
| 84 | - | func LoadTargets(path string) (*ClientConfig, error) { | 84 | + | func LoadRemotes(path string) (*ClientConfig, error) { |
| 85 | if path == "" { | 85 | if path == "" { | ||
| 86 | - | path = DefaultTargetsPath() | 86 | + | path = DefaultRemotesPath() |
| 87 | } | 87 | } | ||
| 88 | 88 | ||||
| 89 | data, err := os.ReadFile(path) | 89 | data, err := os.ReadFile(path) | ||
| 90 | if err != nil { | 90 | if err != nil { | ||
| 91 | if os.IsNotExist(err) { | 91 | if os.IsNotExist(err) { | ||
| 92 | return &ClientConfig{ | 92 | return &ClientConfig{ | ||
| 93 | - | Targets: make(map[string]TargetEntry), | 93 | + | Remotes: make(map[string]RemoteEntry), |
| 94 | }, nil | 94 | }, nil | ||
| 95 | } | 95 | } | ||
| 96 | - | return nil, fmt.Errorf("failed to read targets: %w", err) | 96 | + | return nil, fmt.Errorf("failed to read remotes: %w", err) |
| 97 | } | 97 | } | ||
| 98 | 98 | ||||
| 99 | var config ClientConfig | 99 | var config ClientConfig | ||
| 100 | if err := toml.Unmarshal(data, &config); err != nil { | 100 | if err := toml.Unmarshal(data, &config); err != nil { | ||
| 101 | - | return nil, fmt.Errorf("failed to parse targets: %w", err) | 101 | + | return nil, fmt.Errorf("failed to parse remotes: %w", err) |
| 102 | } | 102 | } | ||
| 103 | 103 | ||||
| 104 | - | if config.Targets == nil { | 104 | + | if config.Remotes == nil { |
| 105 | - | config.Targets = make(map[string]TargetEntry) | 105 | + | config.Remotes = make(map[string]RemoteEntry) |
| 106 | } | 106 | } | ||
| 107 | 107 | ||||
| 108 | return &config, nil | 108 | return &config, nil | ||
| 109 | } | 109 | } | ||
| 110 | 110 | ||||
| 111 | - | // SaveTargets saves the targets configuration. | 111 | + | // SaveRemotes saves the remotes configuration. |
| 112 | - | func SaveTargets(path string, config *ClientConfig) error { | 112 | + | func SaveRemotes(path string, config *ClientConfig) error { |
| 113 | if path == "" { | 113 | if path == "" { | ||
| 114 | - | path = DefaultTargetsPath() | 114 | + | path = DefaultRemotesPath() |
| 115 | } | 115 | } | ||
| 116 | 116 | ||||
| 117 | // Ensure directory exists | 117 | // Ensure directory exists | ||
| @@ -122,51 +122,51 @@ func SaveTargets(path string, config *ClientConfig) error { | |||||
| 122 | 122 | ||||
| 123 | data, err := toml.Marshal(config) | 123 | data, err := toml.Marshal(config) | ||
| 124 | if err != nil { | 124 | if err != nil { | ||
| 125 | - | return fmt.Errorf("failed to marshal targets: %w", err) | 125 | + | return fmt.Errorf("failed to marshal remotes: %w", err) |
| 126 | } | 126 | } | ||
| 127 | 127 | ||||
| 128 | if err := os.WriteFile(path, data, 0600); err != nil { | 128 | if err := os.WriteFile(path, data, 0600); err != nil { | ||
| 129 | - | return fmt.Errorf("failed to write targets: %w", err) | 129 | + | return fmt.Errorf("failed to write remotes: %w", err) |
| 130 | } | 130 | } | ||
| 131 | 131 | ||||
| 132 | return nil | 132 | return nil | ||
| 133 | } | 133 | } | ||
| 134 | 134 | ||||
| 135 | - | // ResolveTarget resolves the target URL and token from configuration. | 135 | + | // ResolveRemote resolves the remote URL and token from configuration. |
| 136 | - | // Precedence: flag > env > config > active target | 136 | + | // Precedence: flag > env > config > active remote |
| 137 | - | func ResolveTarget(flagTarget, flagToken string) (url, token string, isRemote bool) { | 137 | + | func ResolveRemote(flagRemote, flagToken string) (url, token string, isRemote bool) { |
| 138 | config, _ := LoadClientConfig("") | 138 | config, _ := LoadClientConfig("") | ||
| 139 | - | targets, _ := LoadTargets("") | 139 | + | remotes, _ := LoadRemotes("") |
| 140 | 140 | ||||
| 141 | - | url, isRemote = resolveTargetURL(flagTarget, config, targets) | 141 | + | url, isRemote = resolveRemoteURL(flagRemote, config, remotes) |
| 142 | if isRemote { | 142 | if isRemote { | ||
| 143 | - | token = resolveToken(flagToken, config, targets) | 143 | + | token = resolveToken(flagToken, config, remotes) |
| 144 | } | 144 | } | ||
| 145 | 145 | ||||
| 146 | return url, token, isRemote | 146 | return url, token, isRemote | ||
| 147 | } | 147 | } | ||
| 148 | 148 | ||||
| 149 | - | // resolveTargetURL resolves the target URL from various sources. | 149 | + | // resolveRemoteURL resolves the remote URL from various sources. |
| 150 | - | func resolveTargetURL(flagTarget string, config *ClientConfig, targets *ClientConfig) (string, bool) { | 150 | + | func resolveRemoteURL(flagRemote string, config *ClientConfig, remotes *ClientConfig) (string, bool) { |
| 151 | // 1. Check flag | 151 | // 1. Check flag | ||
| 152 | - | if flagTarget != "" { | 152 | + | if flagRemote != "" { |
| 153 | - | return flagTarget, true | 153 | + | return flagRemote, true |
| 154 | } | 154 | } | ||
| 155 | 155 | ||||
| 156 | // 2. Check environment variable | 156 | // 2. Check environment variable | ||
| 157 | - | if envTarget := os.Getenv("GORDON_TARGET"); envTarget != "" { | 157 | + | if envRemote := os.Getenv("GORDON_REMOTE"); envRemote != "" { |
| 158 | - | return envTarget, true | 158 | + | return envRemote, true |
| 159 | } | 159 | } | ||
| 160 | 160 | ||||
| 161 | // 3. Check client config | 161 | // 3. Check client config | ||
| 162 | - | if config != nil && config.Client.Mode == "remote" && config.Client.Target != "" { | 162 | + | if config != nil && config.Client.Mode == "remote" && config.Client.Remote != "" { |
| 163 | - | return config.Client.Target, true | 163 | + | return config.Client.Remote, true |
| 164 | } | 164 | } | ||
| 165 | 165 | ||||
| 166 | - | // 4. Check active target | 166 | + | // 4. Check active remote |
| 167 | - | if targets != nil && targets.Active != "" { | 167 | + | if remotes != nil && remotes.Active != "" { |
| 168 | - | if target, ok := targets.Targets[targets.Active]; ok { | 168 | + | if remote, ok := remotes.Remotes[remotes.Active]; ok { |
| 169 | - | return target.URL, true | 169 | + | return remote.URL, true |
| 170 | } | 170 | } | ||
| 171 | } | 171 | } | ||
| 172 | 172 | ||||
| @@ -174,7 +174,7 @@ func resolveTargetURL(flagTarget string, config *ClientConfig, targets *ClientCo | |||||
| 174 | } | 174 | } | ||
| 175 | 175 | ||||
| 176 | // resolveToken resolves the authentication token from various sources. | 176 | // resolveToken resolves the authentication token from various sources. | ||
| 177 | - | func resolveToken(flagToken string, config *ClientConfig, targets *ClientConfig) string { | 177 | + | func resolveToken(flagToken string, config *ClientConfig, remotes *ClientConfig) string { |
| 178 | // 1. Flag token takes precedence | 178 | // 1. Flag token takes precedence | ||
| 179 | if flagToken != "" { | 179 | if flagToken != "" { | ||
| 180 | return flagToken | 180 | return flagToken | ||
| @@ -195,14 +195,14 @@ func resolveToken(flagToken string, config *ClientConfig, targets *ClientConfig) | |||||
| 195 | } | 195 | } | ||
| 196 | } | 196 | } | ||
| 197 | 197 | ||||
| 198 | - | // 4. Active target token | 198 | + | // 4. Active remote token |
| 199 | - | if targets != nil && targets.Active != "" { | 199 | + | if remotes != nil && remotes.Active != "" { |
| 200 | - | if target, ok := targets.Targets[targets.Active]; ok { | 200 | + | if remote, ok := remotes.Remotes[remotes.Active]; ok { |
| 201 | - | if target.Token != "" { | 201 | + | if remote.Token != "" { |
| 202 | - | return target.Token | 202 | + | return remote.Token |
| 203 | } | 203 | } | ||
| 204 | - | if target.TokenEnv != "" { | 204 | + | if remote.TokenEnv != "" { |
| 205 | - | return os.Getenv(target.TokenEnv) | 205 | + | return os.Getenv(remote.TokenEnv) |
| 206 | } | 206 | } | ||
| 207 | } | 207 | } | ||
| 208 | } | 208 | } | ||
| @@ -210,61 +210,61 @@ func resolveToken(flagToken string, config *ClientConfig, targets *ClientConfig) | |||||
| 210 | return "" | 210 | return "" | ||
| 211 | } | 211 | } | ||
| 212 | 212 | ||||
| 213 | - | // AddTarget adds a new target to the targets configuration. | 213 | + | // AddRemote adds a new remote to the remotes configuration. |
| 214 | - | func AddTarget(name, url, token string) error { | 214 | + | func AddRemote(name, url, token string) error { |
| 215 | - | config, err := LoadTargets("") | 215 | + | config, err := LoadRemotes("") |
| 216 | if err != nil { | 216 | if err != nil { | ||
| 217 | return err | 217 | return err | ||
| 218 | } | 218 | } | ||
| 219 | 219 | ||||
| 220 | - | config.Targets[name] = TargetEntry{ | 220 | + | config.Remotes[name] = RemoteEntry{ |
| 221 | URL: url, | 221 | URL: url, | ||
| 222 | Token: token, | 222 | Token: token, | ||
| 223 | } | 223 | } | ||
| 224 | 224 | ||||
| 225 | - | return SaveTargets("", config) | 225 | + | return SaveRemotes("", config) |
| 226 | } | 226 | } | ||
| 227 | 227 | ||||
| 228 | - | // RemoveTarget removes a target from the targets configuration. | 228 | + | // RemoveRemote removes a remote from the remotes configuration. |
| 229 | - | func RemoveTarget(name string) error { | 229 | + | func RemoveRemote(name string) error { |
| 230 | - | config, err := LoadTargets("") | 230 | + | config, err := LoadRemotes("") |
| 231 | if err != nil { | 231 | if err != nil { | ||
| 232 | return err | 232 | return err | ||
| 233 | } | 233 | } | ||
| 234 | 234 | ||||
| 235 | - | delete(config.Targets, name) | 235 | + | delete(config.Remotes, name) |
| 236 | 236 | ||||
| 237 | - | // Clear active if it was the removed target | 237 | + | // Clear active if it was the removed remote |
| 238 | if config.Active == name { | 238 | if config.Active == name { | ||
| 239 | config.Active = "" | 239 | config.Active = "" | ||
| 240 | } | 240 | } | ||
| 241 | 241 | ||||
| 242 | - | return SaveTargets("", config) | 242 | + | return SaveRemotes("", config) |
| 243 | } | 243 | } | ||
| 244 | 244 | ||||
| 245 | - | // SetActiveTarget sets the active target. | 245 | + | // SetActiveRemote sets the active remote. |
| 246 | - | func SetActiveTarget(name string) error { | 246 | + | func SetActiveRemote(name string) error { |
| 247 | - | config, err := LoadTargets("") | 247 | + | config, err := LoadRemotes("") |
| 248 | if err != nil { | 248 | if err != nil { | ||
| 249 | return err | 249 | return err | ||
| 250 | } | 250 | } | ||
| 251 | 251 | ||||
| 252 | - | // Verify target exists | 252 | + | // Verify remote exists |
| 253 | - | if _, ok := config.Targets[name]; !ok { | 253 | + | if _, ok := config.Remotes[name]; !ok { |
| 254 | - | return fmt.Errorf("target '%s' not found", name) | 254 | + | return fmt.Errorf("remote '%s' not found", name) |
| 255 | } | 255 | } | ||
| 256 | 256 | ||||
| 257 | config.Active = name | 257 | config.Active = name | ||
| 258 | 258 | ||||
| 259 | - | return SaveTargets("", config) | 259 | + | return SaveRemotes("", config) |
| 260 | } | 260 | } | ||
| 261 | 261 | ||||
| 262 | - | // ListTargets returns all saved targets. | 262 | + | // ListRemotes returns all saved remotes. |
| 263 | - | func ListTargets() (map[string]TargetEntry, string, error) { | 263 | + | func ListRemotes() (map[string]RemoteEntry, string, error) { |
| 264 | - | config, err := LoadTargets("") | 264 | + | config, err := LoadRemotes("") |
| 265 | if err != nil { | 265 | if err != nil { | ||
| 266 | return nil, "", err | 266 | return nil, "", err | ||
| 267 | } | 267 | } | ||
| 268 | 268 | ||||
| 269 | - | return config.Targets, config.Active, nil | 269 | + | return config.Remotes, config.Active, nil |
| 270 | } | 270 | } | ||
A internal/adapters/in/cli/remotes.go +246 -0
| @@ -0,0 +1,246 @@ | |||
| 1 | + | package cli | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "fmt" | |
| 5 | + | ||
| 6 | + | "gordon/internal/adapters/in/cli/remote" | |
| 7 | + | "gordon/internal/adapters/in/cli/ui/components" | |
| 8 | + | "gordon/internal/adapters/in/cli/ui/styles" | |
| 9 | + | ||
| 10 | + | "github.com/spf13/cobra" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // newRemotesCmd creates the remotes command group. | |
| 14 | + | func newRemotesCmd() *cobra.Command { | |
| 15 | + | cmd := &cobra.Command{ | |
| 16 | + | Use: "remotes", | |
| 17 | + | Short: "Manage saved remote Gordon instances", | |
| 18 | + | Long: `Manage saved remote Gordon instances. | |
| 19 | + | ||
| 20 | + | Remotes allow you to save frequently used remote Gordon instances | |
| 21 | + | and quickly switch between them. | |
| 22 | + | ||
| 23 | + | Configuration is stored in ~/.config/gordon/remotes.toml`, | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | cmd.AddCommand(newRemotesListCmd()) | |
| 27 | + | cmd.AddCommand(newRemotesAddCmd()) | |
| 28 | + | cmd.AddCommand(newRemotesRemoveCmd()) | |
| 29 | + | cmd.AddCommand(newRemotesUseCmd()) | |
| 30 | + | ||
| 31 | + | return cmd | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | // newRemotesListCmd creates the remotes list command. | |
| 35 | + | func newRemotesListCmd() *cobra.Command { | |
| 36 | + | return &cobra.Command{ | |
| 37 | + | Use: "list", | |
| 38 | + | Short: "List saved remotes", | |
| 39 | + | RunE: func(cmd *cobra.Command, args []string) error { | |
| 40 | + | remotes, active, err := remote.ListRemotes() | |
| 41 | + | if err != nil { | |
| 42 | + | return fmt.Errorf("failed to list remotes: %w", err) | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | if len(remotes) == 0 { | |
| 46 | + | fmt.Println(styles.Theme.Muted.Render("No remotes configured")) | |
| 47 | + | fmt.Println() | |
| 48 | + | fmt.Println("Add a remote with:") | |
| 49 | + | fmt.Println(styles.Theme.Bold.Render(" gordon remotes add <name> <url>")) | |
| 50 | + | return nil | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | fmt.Println(styles.Theme.Title.Render("Saved Remotes")) | |
| 54 | + | fmt.Println() | |
| 55 | + | ||
| 56 | + | // Build table rows | |
| 57 | + | rows := make([][]string, 0, len(remotes)) | |
| 58 | + | for name, r := range remotes { | |
| 59 | + | status := "" | |
| 60 | + | if name == active { | |
| 61 | + | status = styles.Theme.Success.Render("active") | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | // Mask token if present | |
| 65 | + | tokenStatus := styles.Theme.Muted.Render("none") | |
| 66 | + | if r.Token != "" { | |
| 67 | + | tokenStatus = styles.Theme.Success.Render("set") | |
| 68 | + | } else if r.TokenEnv != "" { | |
| 69 | + | tokenStatus = fmt.Sprintf("$%s", r.TokenEnv) | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | rows = append(rows, []string{name, r.URL, tokenStatus, status}) | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | // Render table | |
| 76 | + | table := components.NewTable( | |
| 77 | + | components.WithColumns([]components.TableColumn{ | |
| 78 | + | {Title: "Name", Width: 15}, | |
| 79 | + | {Title: "URL", Width: 35}, | |
| 80 | + | {Title: "Token", Width: 15}, | |
| 81 | + | {Title: "Status", Width: 10}, | |
| 82 | + | }), | |
| 83 | + | components.WithRows(rows), | |
| 84 | + | ) | |
| 85 | + | ||
| 86 | + | fmt.Println(table.View()) | |
| 87 | + | ||
| 88 | + | return nil | |
| 89 | + | }, | |
| 90 | + | } | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | // newRemotesAddCmd creates the remotes add command. | |
| 94 | + | func newRemotesAddCmd() *cobra.Command { | |
| 95 | + | var token string | |
| 96 | + | var tokenEnv string | |
| 97 | + | ||
| 98 | + | cmd := &cobra.Command{ | |
| 99 | + | Use: "add <name> <url>", | |
| 100 | + | Short: "Add a new remote", | |
| 101 | + | Long: `Add a new saved remote. | |
| 102 | + | ||
| 103 | + | Examples: | |
| 104 | + | gordon remotes add prod https://gordon.mydomain.com | |
| 105 | + | gordon remotes add prod https://gordon.mydomain.com --token eyJ... | |
| 106 | + | gordon remotes add staging https://staging.mydomain.com --token-env STAGING_TOKEN`, | |
| 107 | + | Args: cobra.ExactArgs(2), | |
| 108 | + | RunE: func(cmd *cobra.Command, args []string) error { | |
| 109 | + | name := args[0] | |
| 110 | + | url := args[1] | |
| 111 | + | ||
| 112 | + | // If token-env is provided, use that instead of token | |
| 113 | + | finalToken := token | |
| 114 | + | if tokenEnv != "" { | |
| 115 | + | // Store token_env reference instead of actual token | |
| 116 | + | if err := addRemoteWithEnv(name, url, tokenEnv); err != nil { | |
| 117 | + | return fmt.Errorf("failed to add remote: %w", err) | |
| 118 | + | } | |
| 119 | + | } else { | |
| 120 | + | if err := remote.AddRemote(name, url, finalToken); err != nil { | |
| 121 | + | return fmt.Errorf("failed to add remote: %w", err) | |
| 122 | + | } | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Remote added: %s -> %s", name, url))) | |
| 126 | + | ||
| 127 | + | if token == "" && tokenEnv == "" { | |
| 128 | + | fmt.Println(styles.Theme.Muted.Render("Tip: Add a token with --token or --token-env for authenticated access")) | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | return nil | |
| 132 | + | }, | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | cmd.Flags().StringVar(&token, "token", "", "Authentication token") | |
| 136 | + | cmd.Flags().StringVar(&tokenEnv, "token-env", "", "Environment variable containing token") | |
| 137 | + | ||
| 138 | + | return cmd | |
| 139 | + | } | |
| 140 | + | ||
| 141 | + | // addRemoteWithEnv adds a remote with a token environment variable reference. | |
| 142 | + | func addRemoteWithEnv(name, url, tokenEnv string) error { | |
| 143 | + | config, err := remote.LoadRemotes("") | |
| 144 | + | if err != nil { | |
| 145 | + | return err | |
| 146 | + | } | |
| 147 | + | ||
| 148 | + | config.Remotes[name] = remote.RemoteEntry{ | |
| 149 | + | URL: url, | |
| 150 | + | TokenEnv: tokenEnv, | |
| 151 | + | } | |
| 152 | + | ||
| 153 | + | return remote.SaveRemotes("", config) | |
| 154 | + | } | |
| 155 | + | ||
| 156 | + | // newRemotesRemoveCmd creates the remotes remove command. | |
| 157 | + | func newRemotesRemoveCmd() *cobra.Command { | |
| 158 | + | var force bool | |
| 159 | + | ||
| 160 | + | cmd := &cobra.Command{ | |
| 161 | + | Use: "remove <name>", | |
| 162 | + | Aliases: []string{"rm"}, | |
| 163 | + | Short: "Remove a remote", | |
| 164 | + | Args: cobra.ExactArgs(1), | |
| 165 | + | RunE: func(cmd *cobra.Command, args []string) error { | |
| 166 | + | name := args[0] | |
| 167 | + | ||
| 168 | + | // Check if remote exists | |
| 169 | + | remotes, active, err := remote.ListRemotes() | |
| 170 | + | if err != nil { | |
| 171 | + | return fmt.Errorf("failed to list remotes: %w", err) | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | if _, exists := remotes[name]; !exists { | |
| 175 | + | fmt.Println(styles.RenderError(fmt.Sprintf("Remote '%s' not found", name))) | |
| 176 | + | return nil | |
| 177 | + | } | |
| 178 | + | ||
| 179 | + | // Confirm unless --force | |
| 180 | + | if !force { | |
| 181 | + | message := fmt.Sprintf("Remove remote '%s'?", name) | |
| 182 | + | if name == active { | |
| 183 | + | message = fmt.Sprintf("Remove remote '%s'? (currently active)", name) | |
| 184 | + | } | |
| 185 | + | ||
| 186 | + | confirmed, err := components.RunConfirm(message) | |
| 187 | + | if err != nil { | |
| 188 | + | return err | |
| 189 | + | } | |
| 190 | + | if !confirmed { | |
| 191 | + | fmt.Println(styles.Theme.Muted.Render("Cancelled")) | |
| 192 | + | return nil | |
| 193 | + | } | |
| 194 | + | } | |
| 195 | + | ||
| 196 | + | if err := remote.RemoveRemote(name); err != nil { | |
| 197 | + | return fmt.Errorf("failed to remove remote: %w", err) | |
| 198 | + | } | |
| 199 | + | ||
| 200 | + | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Remote removed: %s", name))) | |
| 201 | + | return nil | |
| 202 | + | }, | |
| 203 | + | } | |
| 204 | + | ||
| 205 | + | cmd.Flags().BoolVarP(&force, "force", "f", false, "Skip confirmation") | |
| 206 | + | ||
| 207 | + | return cmd | |
| 208 | + | } | |
| 209 | + | ||
| 210 | + | // newRemotesUseCmd creates the remotes use command. | |
| 211 | + | func newRemotesUseCmd() *cobra.Command { | |
| 212 | + | return &cobra.Command{ | |
| 213 | + | Use: "use <name>", | |
| 214 | + | Short: "Set the active remote", | |
| 215 | + | Long: `Set a remote as the active default. | |
| 216 | + | ||
| 217 | + | When a remote is active, it will be used automatically for remote commands | |
| 218 | + | without needing to specify --remote. | |
| 219 | + | ||
| 220 | + | Examples: | |
| 221 | + | gordon remotes use prod | |
| 222 | + | gordon routes list # Uses prod remote automatically`, | |
| 223 | + | Args: cobra.ExactArgs(1), | |
| 224 | + | RunE: func(cmd *cobra.Command, args []string) error { | |
| 225 | + | name := args[0] | |
| 226 | + | ||
| 227 | + | if err := remote.SetActiveRemote(name); err != nil { | |
| 228 | + | if err.Error() == fmt.Sprintf("remote '%s' not found", name) { | |
| 229 | + | fmt.Println(styles.RenderError(fmt.Sprintf("Remote '%s' not found", name))) | |
| 230 | + | fmt.Println() | |
| 231 | + | fmt.Println("Available remotes:") | |
| 232 | + | ||
| 233 | + | remotes, _, _ := remote.ListRemotes() | |
| 234 | + | for remoteName := range remotes { | |
| 235 | + | fmt.Printf(" %s\n", remoteName) | |
| 236 | + | } | |
| 237 | + | return nil | |
| 238 | + | } | |
| 239 | + | return fmt.Errorf("failed to set active remote: %w", err) | |
| 240 | + | } | |
| 241 | + | ||
| 242 | + | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Active remote set to '%s'", name))) | |
| 243 | + | return nil | |
| 244 | + | }, | |
| 245 | + | } | |
| 246 | + | } | |
| @@ -0,0 +1,246 @@ | |||||
| 1 | + | package cli | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "fmt" | |||
| 5 | + | ||||
| 6 | + | "gordon/internal/adapters/in/cli/remote" | |||
| 7 | + | "gordon/internal/adapters/in/cli/ui/components" | |||
| 8 | + | "gordon/internal/adapters/in/cli/ui/styles" | |||
| 9 | + | ||||
| 10 | + | "github.com/spf13/cobra" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // newRemotesCmd creates the remotes command group. | |||
| 14 | + | func newRemotesCmd() *cobra.Command { | |||
| 15 | + | cmd := &cobra.Command{ | |||
| 16 | + | Use: "remotes", | |||
| 17 | + | Short: "Manage saved remote Gordon instances", | |||
| 18 | + | Long: `Manage saved remote Gordon instances. | |||
| 19 | + | ||||
| 20 | + | Remotes allow you to save frequently used remote Gordon instances | |||
| 21 | + | and quickly switch between them. | |||
| 22 | + | ||||
| 23 | + | Configuration is stored in ~/.config/gordon/remotes.toml`, | |||
| 24 | + | } | |||
| 25 | + | ||||
| 26 | + | cmd.AddCommand(newRemotesListCmd()) | |||
| 27 | + | cmd.AddCommand(newRemotesAddCmd()) | |||
| 28 | + | cmd.AddCommand(newRemotesRemoveCmd()) | |||
| 29 | + | cmd.AddCommand(newRemotesUseCmd()) | |||
| 30 | + | ||||
| 31 | + | return cmd | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | // newRemotesListCmd creates the remotes list command. | |||
| 35 | + | func newRemotesListCmd() *cobra.Command { | |||
| 36 | + | return &cobra.Command{ | |||
| 37 | + | Use: "list", | |||
| 38 | + | Short: "List saved remotes", | |||
| 39 | + | RunE: func(cmd *cobra.Command, args []string) error { | |||
| 40 | + | remotes, active, err := remote.ListRemotes() | |||
| 41 | + | if err != nil { | |||
| 42 | + | return fmt.Errorf("failed to list remotes: %w", err) | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | if len(remotes) == 0 { | |||
| 46 | + | fmt.Println(styles.Theme.Muted.Render("No remotes configured")) | |||
| 47 | + | fmt.Println() | |||
| 48 | + | fmt.Println("Add a remote with:") | |||
| 49 | + | fmt.Println(styles.Theme.Bold.Render(" gordon remotes add <name> <url>")) | |||
| 50 | + | return nil | |||
| 51 | + | } | |||
| 52 | + | ||||
| 53 | + | fmt.Println(styles.Theme.Title.Render("Saved Remotes")) | |||
| 54 | + | fmt.Println() | |||
| 55 | + | ||||
| 56 | + | // Build table rows | |||
| 57 | + | rows := make([][]string, 0, len(remotes)) | |||
| 58 | + | for name, r := range remotes { | |||
| 59 | + | status := "" | |||
| 60 | + | if name == active { | |||
| 61 | + | status = styles.Theme.Success.Render("active") | |||
| 62 | + | } | |||
| 63 | + | ||||
| 64 | + | // Mask token if present | |||
| 65 | + | tokenStatus := styles.Theme.Muted.Render("none") | |||
| 66 | + | if r.Token != "" { | |||
| 67 | + | tokenStatus = styles.Theme.Success.Render("set") | |||
| 68 | + | } else if r.TokenEnv != "" { | |||
| 69 | + | tokenStatus = fmt.Sprintf("$%s", r.TokenEnv) | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | rows = append(rows, []string{name, r.URL, tokenStatus, status}) | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | // Render table | |||
| 76 | + | table := components.NewTable( | |||
| 77 | + | components.WithColumns([]components.TableColumn{ | |||
| 78 | + | {Title: "Name", Width: 15}, | |||
| 79 | + | {Title: "URL", Width: 35}, | |||
| 80 | + | {Title: "Token", Width: 15}, | |||
| 81 | + | {Title: "Status", Width: 10}, | |||
| 82 | + | }), | |||
| 83 | + | components.WithRows(rows), | |||
| 84 | + | ) | |||
| 85 | + | ||||
| 86 | + | fmt.Println(table.View()) | |||
| 87 | + | ||||
| 88 | + | return nil | |||
| 89 | + | }, | |||
| 90 | + | } | |||
| 91 | + | } | |||
| 92 | + | ||||
| 93 | + | // newRemotesAddCmd creates the remotes add command. | |||
| 94 | + | func newRemotesAddCmd() *cobra.Command { | |||
| 95 | + | var token string | |||
| 96 | + | var tokenEnv string | |||
| 97 | + | ||||
| 98 | + | cmd := &cobra.Command{ | |||
| 99 | + | Use: "add <name> <url>", | |||
| 100 | + | Short: "Add a new remote", | |||
| 101 | + | Long: `Add a new saved remote. | |||
| 102 | + | ||||
| 103 | + | Examples: | |||
| 104 | + | gordon remotes add prod https://gordon.mydomain.com | |||
| 105 | + | gordon remotes add prod https://gordon.mydomain.com --token eyJ... | |||
| 106 | + | gordon remotes add staging https://staging.mydomain.com --token-env STAGING_TOKEN`, | |||
| 107 | + | Args: cobra.ExactArgs(2), | |||
| 108 | + | RunE: func(cmd *cobra.Command, args []string) error { | |||
| 109 | + | name := args[0] | |||
| 110 | + | url := args[1] | |||
| 111 | + | ||||
| 112 | + | // If token-env is provided, use that instead of token | |||
| 113 | + | finalToken := token | |||
| 114 | + | if tokenEnv != "" { | |||
| 115 | + | // Store token_env reference instead of actual token | |||
| 116 | + | if err := addRemoteWithEnv(name, url, tokenEnv); err != nil { | |||
| 117 | + | return fmt.Errorf("failed to add remote: %w", err) | |||
| 118 | + | } | |||
| 119 | + | } else { | |||
| 120 | + | if err := remote.AddRemote(name, url, finalToken); err != nil { | |||
| 121 | + | return fmt.Errorf("failed to add remote: %w", err) | |||
| 122 | + | } | |||
| 123 | + | } | |||
| 124 | + | ||||
| 125 | + | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Remote added: %s -> %s", name, url))) | |||
| 126 | + | ||||
| 127 | + | if token == "" && tokenEnv == "" { | |||
| 128 | + | fmt.Println(styles.Theme.Muted.Render("Tip: Add a token with --token or --token-env for authenticated access")) | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | return nil | |||
| 132 | + | }, | |||
| 133 | + | } | |||
| 134 | + | ||||
| 135 | + | cmd.Flags().StringVar(&token, "token", "", "Authentication token") | |||
| 136 | + | cmd.Flags().StringVar(&tokenEnv, "token-env", "", "Environment variable containing token") | |||
| 137 | + | ||||
| 138 | + | return cmd | |||
| 139 | + | } | |||
| 140 | + | ||||
| 141 | + | // addRemoteWithEnv adds a remote with a token environment variable reference. | |||
| 142 | + | func addRemoteWithEnv(name, url, tokenEnv string) error { | |||
| 143 | + | config, err := remote.LoadRemotes("") | |||
| 144 | + | if err != nil { | |||
| 145 | + | return err | |||
| 146 | + | } | |||
| 147 | + | ||||
| 148 | + | config.Remotes[name] = remote.RemoteEntry{ | |||
| 149 | + | URL: url, | |||
| 150 | + | TokenEnv: tokenEnv, | |||
| 151 | + | } | |||
| 152 | + | ||||
| 153 | + | return remote.SaveRemotes("", config) | |||
| 154 | + | } | |||
| 155 | + | ||||
| 156 | + | // newRemotesRemoveCmd creates the remotes remove command. | |||
| 157 | + | func newRemotesRemoveCmd() *cobra.Command { | |||
| 158 | + | var force bool | |||
| 159 | + | ||||
| 160 | + | cmd := &cobra.Command{ | |||
| 161 | + | Use: "remove <name>", | |||
| 162 | + | Aliases: []string{"rm"}, | |||
| 163 | + | Short: "Remove a remote", | |||
| 164 | + | Args: cobra.ExactArgs(1), | |||
| 165 | + | RunE: func(cmd *cobra.Command, args []string) error { | |||
| 166 | + | name := args[0] | |||
| 167 | + | ||||
| 168 | + | // Check if remote exists | |||
| 169 | + | remotes, active, err := remote.ListRemotes() | |||
| 170 | + | if err != nil { | |||
| 171 | + | return fmt.Errorf("failed to list remotes: %w", err) | |||
| 172 | + | } | |||
| 173 | + | ||||
| 174 | + | if _, exists := remotes[name]; !exists { | |||
| 175 | + | fmt.Println(styles.RenderError(fmt.Sprintf("Remote '%s' not found", name))) | |||
| 176 | + | return nil | |||
| 177 | + | } | |||
| 178 | + | ||||
| 179 | + | // Confirm unless --force | |||
| 180 | + | if !force { | |||
| 181 | + | message := fmt.Sprintf("Remove remote '%s'?", name) | |||
| 182 | + | if name == active { | |||
| 183 | + | message = fmt.Sprintf("Remove remote '%s'? (currently active)", name) | |||
| 184 | + | } | |||
| 185 | + | ||||
| 186 | + | confirmed, err := components.RunConfirm(message) | |||
| 187 | + | if err != nil { | |||
| 188 | + | return err | |||
| 189 | + | } | |||
| 190 | + | if !confirmed { | |||
| 191 | + | fmt.Println(styles.Theme.Muted.Render("Cancelled")) | |||
| 192 | + | return nil | |||
| 193 | + | } | |||
| 194 | + | } | |||
| 195 | + | ||||
| 196 | + | if err := remote.RemoveRemote(name); err != nil { | |||
| 197 | + | return fmt.Errorf("failed to remove remote: %w", err) | |||
| 198 | + | } | |||
| 199 | + | ||||
| 200 | + | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Remote removed: %s", name))) | |||
| 201 | + | return nil | |||
| 202 | + | }, | |||
| 203 | + | } | |||
| 204 | + | ||||
| 205 | + | cmd.Flags().BoolVarP(&force, "force", "f", false, "Skip confirmation") | |||
| 206 | + | ||||
| 207 | + | return cmd | |||
| 208 | + | } | |||
| 209 | + | ||||
| 210 | + | // newRemotesUseCmd creates the remotes use command. | |||
| 211 | + | func newRemotesUseCmd() *cobra.Command { | |||
| 212 | + | return &cobra.Command{ | |||
| 213 | + | Use: "use <name>", | |||
| 214 | + | Short: "Set the active remote", | |||
| 215 | + | Long: `Set a remote as the active default. | |||
| 216 | + | ||||
| 217 | + | When a remote is active, it will be used automatically for remote commands | |||
| 218 | + | without needing to specify --remote. | |||
| 219 | + | ||||
| 220 | + | Examples: | |||
| 221 | + | gordon remotes use prod | |||
| 222 | + | gordon routes list # Uses prod remote automatically`, | |||
| 223 | + | Args: cobra.ExactArgs(1), | |||
| 224 | + | RunE: func(cmd *cobra.Command, args []string) error { | |||
| 225 | + | name := args[0] | |||
| 226 | + | ||||
| 227 | + | if err := remote.SetActiveRemote(name); err != nil { | |||
| 228 | + | if err.Error() == fmt.Sprintf("remote '%s' not found", name) { | |||
| 229 | + | fmt.Println(styles.RenderError(fmt.Sprintf("Remote '%s' not found", name))) | |||
| 230 | + | fmt.Println() | |||
| 231 | + | fmt.Println("Available remotes:") | |||
| 232 | + | ||||
| 233 | + | remotes, _, _ := remote.ListRemotes() | |||
| 234 | + | for remoteName := range remotes { | |||
| 235 | + | fmt.Printf(" %s\n", remoteName) | |||
| 236 | + | } | |||
| 237 | + | return nil | |||
| 238 | + | } | |||
| 239 | + | return fmt.Errorf("failed to set active remote: %w", err) | |||
| 240 | + | } | |||
| 241 | + | ||||
| 242 | + | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Active remote set to '%s'", name))) | |||
| 243 | + | return nil | |||
| 244 | + | }, | |||
| 245 | + | } | |||
| 246 | + | } | |||
M internal/adapters/in/cli/root.go +8 -8
| @@ -16,7 +16,7 @@ var ( | |||
| 16 | 16 | BuildDate = "unknown" | |
| 17 | 17 | ||
| 18 | 18 | // Global flags for remote targeting | |
| 19 | - | targetFlag string | |
| 19 | + | remoteFlag string | |
| 20 | 20 | tokenFlag string | |
| 21 | 21 | ) | |
| 22 | 22 | ||
| @@ -31,13 +31,13 @@ a Docker registry with automatic container deployment capabilities. | |||
| 31 | 31 | It listens for image pushes and automatically deploys containers based on | |
| 32 | 32 | configuration rules, making it ideal for single-server deployments. | |
| 33 | 33 | ||
| 34 | - | The CLI can target remote Gordon instances using the --target flag or | |
| 35 | - | GORDON_TARGET environment variable.`, | |
| 34 | + | The CLI can target remote Gordon instances using the --remote flag or | |
| 35 | + | GORDON_REMOTE environment variable.`, | |
| 36 | 36 | } | |
| 37 | 37 | ||
| 38 | 38 | // Add persistent flags for remote targeting | |
| 39 | - | rootCmd.PersistentFlags().StringVar(&targetFlag, "target", "", "Remote Gordon URL (e.g., https://gordon.mydomain.com)") | |
| 40 | - | rootCmd.PersistentFlags().StringVar(&tokenFlag, "token", "", "Authentication token for remote target") | |
| 39 | + | rootCmd.PersistentFlags().StringVar(&remoteFlag, "remote", "", "Remote Gordon URL (e.g., https://gordon.mydomain.com)") | |
| 40 | + | rootCmd.PersistentFlags().StringVar(&tokenFlag, "token", "", "Authentication token for remote") | |
| 41 | 41 | ||
| 42 | 42 | // Server commands | |
| 43 | 43 | rootCmd.AddCommand(newServeCmd()) | |
| @@ -51,7 +51,7 @@ GORDON_TARGET environment variable.`, | |||
| 51 | 51 | // Remote management commands | |
| 52 | 52 | rootCmd.AddCommand(newRoutesCmd()) | |
| 53 | 53 | rootCmd.AddCommand(newSecretsCmd()) | |
| 54 | - | rootCmd.AddCommand(newTargetsCmd()) | |
| 54 | + | rootCmd.AddCommand(newRemotesCmd()) | |
| 55 | 55 | rootCmd.AddCommand(newStatusCmd()) | |
| 56 | 56 | ||
| 57 | 57 | return rootCmd | |
| @@ -60,7 +60,7 @@ GORDON_TARGET environment variable.`, | |||
| 60 | 60 | // GetRemoteClient returns a remote client if targeting a remote instance, | |
| 61 | 61 | // or nil if running locally. | |
| 62 | 62 | func GetRemoteClient() (*remote.Client, bool) { | |
| 63 | - | url, token, isRemote := remote.ResolveTarget(targetFlag, tokenFlag) | |
| 63 | + | url, token, isRemote := remote.ResolveRemote(remoteFlag, tokenFlag) | |
| 64 | 64 | if !isRemote { | |
| 65 | 65 | return nil, false | |
| 66 | 66 | } | |
| @@ -71,7 +71,7 @@ func GetRemoteClient() (*remote.Client, bool) { | |||
| 71 | 71 | ||
| 72 | 72 | // IsRemoteMode returns true if CLI is targeting a remote Gordon instance. | |
| 73 | 73 | func IsRemoteMode() bool { | |
| 74 | - | _, _, isRemote := remote.ResolveTarget(targetFlag, tokenFlag) | |
| 74 | + | _, _, isRemote := remote.ResolveRemote(remoteFlag, tokenFlag) | |
| 75 | 75 | return isRemote | |
| 76 | 76 | } | |
| 77 | 77 | ||
| @@ -16,7 +16,7 @@ var ( | |||||
| 16 | BuildDate = "unknown" | 16 | BuildDate = "unknown" | ||
| 17 | 17 | ||||
| 18 | // Global flags for remote targeting | 18 | // Global flags for remote targeting | ||
| 19 | - | targetFlag string | 19 | + | remoteFlag string |
| 20 | tokenFlag string | 20 | tokenFlag string | ||
| 21 | ) | 21 | ) | ||
| 22 | 22 | ||||
| @@ -31,13 +31,13 @@ a Docker registry with automatic container deployment capabilities. | |||||
| 31 | It listens for image pushes and automatically deploys containers based on | 31 | It listens for image pushes and automatically deploys containers based on | ||
| 32 | configuration rules, making it ideal for single-server deployments. | 32 | configuration rules, making it ideal for single-server deployments. | ||
| 33 | 33 | ||||
| 34 | - | The CLI can target remote Gordon instances using the --target flag or | 34 | + | The CLI can target remote Gordon instances using the --remote flag or |
| 35 | - | GORDON_TARGET environment variable.`, | 35 | + | GORDON_REMOTE environment variable.`, |
| 36 | } | 36 | } | ||
| 37 | 37 | ||||
| 38 | // Add persistent flags for remote targeting | 38 | // Add persistent flags for remote targeting | ||
| 39 | - | rootCmd.PersistentFlags().StringVar(&targetFlag, "target", "", "Remote Gordon URL (e.g., https://gordon.mydomain.com)") | 39 | + | rootCmd.PersistentFlags().StringVar(&remoteFlag, "remote", "", "Remote Gordon URL (e.g., https://gordon.mydomain.com)") |
| 40 | - | rootCmd.PersistentFlags().StringVar(&tokenFlag, "token", "", "Authentication token for remote target") | 40 | + | rootCmd.PersistentFlags().StringVar(&tokenFlag, "token", "", "Authentication token for remote") |
| 41 | 41 | ||||
| 42 | // Server commands | 42 | // Server commands | ||
| 43 | rootCmd.AddCommand(newServeCmd()) | 43 | rootCmd.AddCommand(newServeCmd()) | ||
| @@ -51,7 +51,7 @@ GORDON_TARGET environment variable.`, | |||||
| 51 | // Remote management commands | 51 | // Remote management commands | ||
| 52 | rootCmd.AddCommand(newRoutesCmd()) | 52 | rootCmd.AddCommand(newRoutesCmd()) | ||
| 53 | rootCmd.AddCommand(newSecretsCmd()) | 53 | rootCmd.AddCommand(newSecretsCmd()) | ||
| 54 | - | rootCmd.AddCommand(newTargetsCmd()) | 54 | + | rootCmd.AddCommand(newRemotesCmd()) |
| 55 | rootCmd.AddCommand(newStatusCmd()) | 55 | rootCmd.AddCommand(newStatusCmd()) | ||
| 56 | 56 | ||||
| 57 | return rootCmd | 57 | return rootCmd | ||
| @@ -60,7 +60,7 @@ GORDON_TARGET environment variable.`, | |||||
| 60 | // GetRemoteClient returns a remote client if targeting a remote instance, | 60 | // GetRemoteClient returns a remote client if targeting a remote instance, | ||
| 61 | // or nil if running locally. | 61 | // or nil if running locally. | ||
| 62 | func GetRemoteClient() (*remote.Client, bool) { | 62 | func GetRemoteClient() (*remote.Client, bool) { | ||
| 63 | - | url, token, isRemote := remote.ResolveTarget(targetFlag, tokenFlag) | 63 | + | url, token, isRemote := remote.ResolveRemote(remoteFlag, tokenFlag) |
| 64 | if !isRemote { | 64 | if !isRemote { | ||
| 65 | return nil, false | 65 | return nil, false | ||
| 66 | } | 66 | } | ||
| @@ -71,7 +71,7 @@ func GetRemoteClient() (*remote.Client, bool) { | |||||
| 71 | 71 | ||||
| 72 | // IsRemoteMode returns true if CLI is targeting a remote Gordon instance. | 72 | // IsRemoteMode returns true if CLI is targeting a remote Gordon instance. | ||
| 73 | func IsRemoteMode() bool { | 73 | func IsRemoteMode() bool { | ||
| 74 | - | _, _, isRemote := remote.ResolveTarget(targetFlag, tokenFlag) | 74 | + | _, _, isRemote := remote.ResolveRemote(remoteFlag, tokenFlag) |
| 75 | return isRemote | 75 | return isRemote | ||
| 76 | } | 76 | } | ||
| 77 | 77 | ||||
D internal/adapters/in/cli/targets.go +0 -246
| @@ -1,246 +0,0 @@ | |||
| 1 | - | package cli | |
| 2 | - | ||
| 3 | - | import ( | |
| 4 | - | "fmt" | |
| 5 | - | ||
| 6 | - | "gordon/internal/adapters/in/cli/remote" | |
| 7 | - | "gordon/internal/adapters/in/cli/ui/components" | |
| 8 | - | "gordon/internal/adapters/in/cli/ui/styles" | |
| 9 | - | ||
| 10 | - | "github.com/spf13/cobra" | |
| 11 | - | ) | |
| 12 | - | ||
| 13 | - | // newTargetsCmd creates the targets command group. | |
| 14 | - | func newTargetsCmd() *cobra.Command { | |
| 15 | - | cmd := &cobra.Command{ | |
| 16 | - | Use: "targets", | |
| 17 | - | Short: "Manage saved remote targets", | |
| 18 | - | Long: `Manage saved remote Gordon targets. | |
| 19 | - | ||
| 20 | - | Targets allow you to save frequently used remote Gordon instances | |
| 21 | - | and quickly switch between them. | |
| 22 | - | ||
| 23 | - | Configuration is stored in ~/.config/gordon/targets.toml`, | |
| 24 | - | } | |
| 25 | - | ||
| 26 | - | cmd.AddCommand(newTargetsListCmd()) | |
| 27 | - | cmd.AddCommand(newTargetsAddCmd()) | |
| 28 | - | cmd.AddCommand(newTargetsRemoveCmd()) | |
| 29 | - | cmd.AddCommand(newTargetsUseCmd()) | |
| 30 | - | ||
| 31 | - | return cmd | |
| 32 | - | } | |
| 33 | - | ||
| 34 | - | // newTargetsListCmd creates the targets list command. | |
| 35 | - | func newTargetsListCmd() *cobra.Command { | |
| 36 | - | return &cobra.Command{ | |
| 37 | - | Use: "list", | |
| 38 | - | Short: "List saved targets", | |
| 39 | - | RunE: func(cmd *cobra.Command, args []string) error { | |
| 40 | - | targets, active, err := remote.ListTargets() | |
| 41 | - | if err != nil { | |
| 42 | - | return fmt.Errorf("failed to list targets: %w", err) | |
| 43 | - | } | |
| 44 | - | ||
| 45 | - | if len(targets) == 0 { | |
| 46 | - | fmt.Println(styles.Theme.Muted.Render("No targets configured")) | |
| 47 | - | fmt.Println() | |
| 48 | - | fmt.Println("Add a target with:") | |
| 49 | - | fmt.Println(styles.Theme.Bold.Render(" gordon targets add <name> <url>")) | |
| 50 | - | return nil | |
| 51 | - | } | |
| 52 | - | ||
| 53 | - | fmt.Println(styles.Theme.Title.Render("Saved Targets")) | |
| 54 | - | fmt.Println() | |
| 55 | - | ||
| 56 | - | // Build table rows | |
| 57 | - | rows := make([][]string, 0, len(targets)) | |
| 58 | - | for name, target := range targets { | |
| 59 | - | status := "" | |
| 60 | - | if name == active { | |
| 61 | - | status = styles.Theme.Success.Render("active") | |
| 62 | - | } | |
| 63 | - | ||
| 64 | - | // Mask token if present | |
| 65 | - | tokenStatus := styles.Theme.Muted.Render("none") | |
| 66 | - | if target.Token != "" { | |
| 67 | - | tokenStatus = styles.Theme.Success.Render("set") | |
| 68 | - | } else if target.TokenEnv != "" { | |
| 69 | - | tokenStatus = fmt.Sprintf("$%s", target.TokenEnv) | |
| 70 | - | } | |
| 71 | - | ||
| 72 | - | rows = append(rows, []string{name, target.URL, tokenStatus, status}) | |
| 73 | - | } | |
| 74 | - | ||
| 75 | - | // Render table | |
| 76 | - | table := components.NewTable( | |
| 77 | - | components.WithColumns([]components.TableColumn{ | |
| 78 | - | {Title: "Name", Width: 15}, | |
| 79 | - | {Title: "URL", Width: 35}, | |
| 80 | - | {Title: "Token", Width: 15}, | |
| 81 | - | {Title: "Status", Width: 10}, | |
| 82 | - | }), | |
| 83 | - | components.WithRows(rows), | |
| 84 | - | ) | |
| 85 | - | ||
| 86 | - | fmt.Println(table.View()) | |
| 87 | - | ||
| 88 | - | return nil | |
| 89 | - | }, | |
| 90 | - | } | |
| 91 | - | } | |
| 92 | - | ||
| 93 | - | // newTargetsAddCmd creates the targets add command. | |
| 94 | - | func newTargetsAddCmd() *cobra.Command { | |
| 95 | - | var token string | |
| 96 | - | var tokenEnv string | |
| 97 | - | ||
| 98 | - | cmd := &cobra.Command{ | |
| 99 | - | Use: "add <name> <url>", | |
| 100 | - | Short: "Add a new target", | |
| 101 | - | Long: `Add a new saved target. | |
| 102 | - | ||
| 103 | - | Examples: | |
| 104 | - | gordon targets add prod https://gordon.mydomain.com | |
| 105 | - | gordon targets add prod https://gordon.mydomain.com --token eyJ... | |
| 106 | - | gordon targets add staging https://staging.mydomain.com --token-env STAGING_TOKEN`, | |
| 107 | - | Args: cobra.ExactArgs(2), | |
| 108 | - | RunE: func(cmd *cobra.Command, args []string) error { | |
| 109 | - | name := args[0] | |
| 110 | - | url := args[1] | |
| 111 | - | ||
| 112 | - | // If token-env is provided, use that instead of token | |
| 113 | - | finalToken := token | |
| 114 | - | if tokenEnv != "" { | |
| 115 | - | // Store token_env reference instead of actual token | |
| 116 | - | if err := addTargetWithEnv(name, url, tokenEnv); err != nil { | |
| 117 | - | return fmt.Errorf("failed to add target: %w", err) | |
| 118 | - | } | |
| 119 | - | } else { | |
| 120 | - | if err := remote.AddTarget(name, url, finalToken); err != nil { | |
| 121 | - | return fmt.Errorf("failed to add target: %w", err) | |
| 122 | - | } | |
| 123 | - | } | |
| 124 | - | ||
| 125 | - | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Target added: %s -> %s", name, url))) | |
| 126 | - | ||
| 127 | - | if token == "" && tokenEnv == "" { | |
| 128 | - | fmt.Println(styles.Theme.Muted.Render("Tip: Add a token with --token or --token-env for authenticated access")) | |
| 129 | - | } | |
| 130 | - | ||
| 131 | - | return nil | |
| 132 | - | }, | |
| 133 | - | } | |
| 134 | - | ||
| 135 | - | cmd.Flags().StringVar(&token, "token", "", "Authentication token") | |
| 136 | - | cmd.Flags().StringVar(&tokenEnv, "token-env", "", "Environment variable containing token") | |
| 137 | - | ||
| 138 | - | return cmd | |
| 139 | - | } | |
| 140 | - | ||
| 141 | - | // addTargetWithEnv adds a target with a token environment variable reference. | |
| 142 | - | func addTargetWithEnv(name, url, tokenEnv string) error { | |
| 143 | - | config, err := remote.LoadTargets("") | |
| 144 | - | if err != nil { | |
| 145 | - | return err | |
| 146 | - | } | |
| 147 | - | ||
| 148 | - | config.Targets[name] = remote.TargetEntry{ | |
| 149 | - | URL: url, | |
| 150 | - | TokenEnv: tokenEnv, | |
| 151 | - | } | |
| 152 | - | ||
| 153 | - | return remote.SaveTargets("", config) | |
| 154 | - | } | |
| 155 | - | ||
| 156 | - | // newTargetsRemoveCmd creates the targets remove command. | |
| 157 | - | func newTargetsRemoveCmd() *cobra.Command { | |
| 158 | - | var force bool | |
| 159 | - | ||
| 160 | - | cmd := &cobra.Command{ | |
| 161 | - | Use: "remove <name>", | |
| 162 | - | Aliases: []string{"rm"}, | |
| 163 | - | Short: "Remove a target", | |
| 164 | - | Args: cobra.ExactArgs(1), | |
| 165 | - | RunE: func(cmd *cobra.Command, args []string) error { | |
| 166 | - | name := args[0] | |
| 167 | - | ||
| 168 | - | // Check if target exists | |
| 169 | - | targets, active, err := remote.ListTargets() | |
| 170 | - | if err != nil { | |
| 171 | - | return fmt.Errorf("failed to list targets: %w", err) | |
| 172 | - | } | |
| 173 | - | ||
| 174 | - | if _, exists := targets[name]; !exists { | |
| 175 | - | fmt.Println(styles.RenderError(fmt.Sprintf("Target '%s' not found", name))) | |
| 176 | - | return nil | |
| 177 | - | } | |
| 178 | - | ||
| 179 | - | // Confirm unless --force | |
| 180 | - | if !force { | |
| 181 | - | message := fmt.Sprintf("Remove target '%s'?", name) | |
| 182 | - | if name == active { | |
| 183 | - | message = fmt.Sprintf("Remove target '%s'? (currently active)", name) | |
| 184 | - | } | |
| 185 | - | ||
| 186 | - | confirmed, err := components.RunConfirm(message) | |
| 187 | - | if err != nil { | |
| 188 | - | return err | |
| 189 | - | } | |
| 190 | - | if !confirmed { | |
| 191 | - | fmt.Println(styles.Theme.Muted.Render("Cancelled")) | |
| 192 | - | return nil | |
| 193 | - | } | |
| 194 | - | } | |
| 195 | - | ||
| 196 | - | if err := remote.RemoveTarget(name); err != nil { | |
| 197 | - | return fmt.Errorf("failed to remove target: %w", err) | |
| 198 | - | } | |
| 199 | - | ||
| 200 | - | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Target removed: %s", name))) | |
| 201 | - | return nil | |
| 202 | - | }, | |
| 203 | - | } | |
| 204 | - | ||
| 205 | - | cmd.Flags().BoolVarP(&force, "force", "f", false, "Skip confirmation") | |
| 206 | - | ||
| 207 | - | return cmd | |
| 208 | - | } | |
| 209 | - | ||
| 210 | - | // newTargetsUseCmd creates the targets use command. | |
| 211 | - | func newTargetsUseCmd() *cobra.Command { | |
| 212 | - | return &cobra.Command{ | |
| 213 | - | Use: "use <name>", | |
| 214 | - | Short: "Set the active target", | |
| 215 | - | Long: `Set a target as the active default. | |
| 216 | - | ||
| 217 | - | When a target is active, it will be used automatically for remote commands | |
| 218 | - | without needing to specify --target. | |
| 219 | - | ||
| 220 | - | Examples: | |
| 221 | - | gordon targets use prod | |
| 222 | - | gordon routes list # Uses prod target automatically`, | |
| 223 | - | Args: cobra.ExactArgs(1), | |
| 224 | - | RunE: func(cmd *cobra.Command, args []string) error { | |
| 225 | - | name := args[0] | |
| 226 | - | ||
| 227 | - | if err := remote.SetActiveTarget(name); err != nil { | |
| 228 | - | if err.Error() == fmt.Sprintf("target '%s' not found", name) { | |
| 229 | - | fmt.Println(styles.RenderError(fmt.Sprintf("Target '%s' not found", name))) | |
| 230 | - | fmt.Println() | |
| 231 | - | fmt.Println("Available targets:") | |
| 232 | - | ||
| 233 | - | targets, _, _ := remote.ListTargets() | |
| 234 | - | for targetName := range targets { | |
| 235 | - | fmt.Printf(" %s\n", targetName) | |
| 236 | - | } | |
| 237 | - | return nil | |
| 238 | - | } | |
| 239 | - | return fmt.Errorf("failed to set active target: %w", err) | |
| 240 | - | } | |
| 241 | - | ||
| 242 | - | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Active target set to '%s'", name))) | |
| 243 | - | return nil | |
| 244 | - | }, | |
| 245 | - | } | |
| 246 | - | } | |
| @@ -1,246 +0,0 @@ | |||||
| 1 | - | package cli | |||
| 2 | - | ||||
| 3 | - | import ( | |||
| 4 | - | "fmt" | |||
| 5 | - | ||||
| 6 | - | "gordon/internal/adapters/in/cli/remote" | |||
| 7 | - | "gordon/internal/adapters/in/cli/ui/components" | |||
| 8 | - | "gordon/internal/adapters/in/cli/ui/styles" | |||
| 9 | - | ||||
| 10 | - | "github.com/spf13/cobra" | |||
| 11 | - | ) | |||
| 12 | - | ||||
| 13 | - | // newTargetsCmd creates the targets command group. | |||
| 14 | - | func newTargetsCmd() *cobra.Command { | |||
| 15 | - | cmd := &cobra.Command{ | |||
| 16 | - | Use: "targets", | |||
| 17 | - | Short: "Manage saved remote targets", | |||
| 18 | - | Long: `Manage saved remote Gordon targets. | |||
| 19 | - | ||||
| 20 | - | Targets allow you to save frequently used remote Gordon instances | |||
| 21 | - | and quickly switch between them. | |||
| 22 | - | ||||
| 23 | - | Configuration is stored in ~/.config/gordon/targets.toml`, | |||
| 24 | - | } | |||
| 25 | - | ||||
| 26 | - | cmd.AddCommand(newTargetsListCmd()) | |||
| 27 | - | cmd.AddCommand(newTargetsAddCmd()) | |||
| 28 | - | cmd.AddCommand(newTargetsRemoveCmd()) | |||
| 29 | - | cmd.AddCommand(newTargetsUseCmd()) | |||
| 30 | - | ||||
| 31 | - | return cmd | |||
| 32 | - | } | |||
| 33 | - | ||||
| 34 | - | // newTargetsListCmd creates the targets list command. | |||
| 35 | - | func newTargetsListCmd() *cobra.Command { | |||
| 36 | - | return &cobra.Command{ | |||
| 37 | - | Use: "list", | |||
| 38 | - | Short: "List saved targets", | |||
| 39 | - | RunE: func(cmd *cobra.Command, args []string) error { | |||
| 40 | - | targets, active, err := remote.ListTargets() | |||
| 41 | - | if err != nil { | |||
| 42 | - | return fmt.Errorf("failed to list targets: %w", err) | |||
| 43 | - | } | |||
| 44 | - | ||||
| 45 | - | if len(targets) == 0 { | |||
| 46 | - | fmt.Println(styles.Theme.Muted.Render("No targets configured")) | |||
| 47 | - | fmt.Println() | |||
| 48 | - | fmt.Println("Add a target with:") | |||
| 49 | - | fmt.Println(styles.Theme.Bold.Render(" gordon targets add <name> <url>")) | |||
| 50 | - | return nil | |||
| 51 | - | } | |||
| 52 | - | ||||
| 53 | - | fmt.Println(styles.Theme.Title.Render("Saved Targets")) | |||
| 54 | - | fmt.Println() | |||
| 55 | - | ||||
| 56 | - | // Build table rows | |||
| 57 | - | rows := make([][]string, 0, len(targets)) | |||
| 58 | - | for name, target := range targets { | |||
| 59 | - | status := "" | |||
| 60 | - | if name == active { | |||
| 61 | - | status = styles.Theme.Success.Render("active") | |||
| 62 | - | } | |||
| 63 | - | ||||
| 64 | - | // Mask token if present | |||
| 65 | - | tokenStatus := styles.Theme.Muted.Render("none") | |||
| 66 | - | if target.Token != "" { | |||
| 67 | - | tokenStatus = styles.Theme.Success.Render("set") | |||
| 68 | - | } else if target.TokenEnv != "" { | |||
| 69 | - | tokenStatus = fmt.Sprintf("$%s", target.TokenEnv) | |||
| 70 | - | } | |||
| 71 | - | ||||
| 72 | - | rows = append(rows, []string{name, target.URL, tokenStatus, status}) | |||
| 73 | - | } | |||
| 74 | - | ||||
| 75 | - | // Render table | |||
| 76 | - | table := components.NewTable( | |||
| 77 | - | components.WithColumns([]components.TableColumn{ | |||
| 78 | - | {Title: "Name", Width: 15}, | |||
| 79 | - | {Title: "URL", Width: 35}, | |||
| 80 | - | {Title: "Token", Width: 15}, | |||
| 81 | - | {Title: "Status", Width: 10}, | |||
| 82 | - | }), | |||
| 83 | - | components.WithRows(rows), | |||
| 84 | - | ) | |||
| 85 | - | ||||
| 86 | - | fmt.Println(table.View()) | |||
| 87 | - | ||||
| 88 | - | return nil | |||
| 89 | - | }, | |||
| 90 | - | } | |||
| 91 | - | } | |||
| 92 | - | ||||
| 93 | - | // newTargetsAddCmd creates the targets add command. | |||
| 94 | - | func newTargetsAddCmd() *cobra.Command { | |||
| 95 | - | var token string | |||
| 96 | - | var tokenEnv string | |||
| 97 | - | ||||
| 98 | - | cmd := &cobra.Command{ | |||
| 99 | - | Use: "add <name> <url>", | |||
| 100 | - | Short: "Add a new target", | |||
| 101 | - | Long: `Add a new saved target. | |||
| 102 | - | ||||
| 103 | - | Examples: | |||
| 104 | - | gordon targets add prod https://gordon.mydomain.com | |||
| 105 | - | gordon targets add prod https://gordon.mydomain.com --token eyJ... | |||
| 106 | - | gordon targets add staging https://staging.mydomain.com --token-env STAGING_TOKEN`, | |||
| 107 | - | Args: cobra.ExactArgs(2), | |||
| 108 | - | RunE: func(cmd *cobra.Command, args []string) error { | |||
| 109 | - | name := args[0] | |||
| 110 | - | url := args[1] | |||
| 111 | - | ||||
| 112 | - | // If token-env is provided, use that instead of token | |||
| 113 | - | finalToken := token | |||
| 114 | - | if tokenEnv != "" { | |||
| 115 | - | // Store token_env reference instead of actual token | |||
| 116 | - | if err := addTargetWithEnv(name, url, tokenEnv); err != nil { | |||
| 117 | - | return fmt.Errorf("failed to add target: %w", err) | |||
| 118 | - | } | |||
| 119 | - | } else { | |||
| 120 | - | if err := remote.AddTarget(name, url, finalToken); err != nil { | |||
| 121 | - | return fmt.Errorf("failed to add target: %w", err) | |||
| 122 | - | } | |||
| 123 | - | } | |||
| 124 | - | ||||
| 125 | - | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Target added: %s -> %s", name, url))) | |||
| 126 | - | ||||
| 127 | - | if token == "" && tokenEnv == "" { | |||
| 128 | - | fmt.Println(styles.Theme.Muted.Render("Tip: Add a token with --token or --token-env for authenticated access")) | |||
| 129 | - | } | |||
| 130 | - | ||||
| 131 | - | return nil | |||
| 132 | - | }, | |||
| 133 | - | } | |||
| 134 | - | ||||
| 135 | - | cmd.Flags().StringVar(&token, "token", "", "Authentication token") | |||
| 136 | - | cmd.Flags().StringVar(&tokenEnv, "token-env", "", "Environment variable containing token") | |||
| 137 | - | ||||
| 138 | - | return cmd | |||
| 139 | - | } | |||
| 140 | - | ||||
| 141 | - | // addTargetWithEnv adds a target with a token environment variable reference. | |||
| 142 | - | func addTargetWithEnv(name, url, tokenEnv string) error { | |||
| 143 | - | config, err := remote.LoadTargets("") | |||
| 144 | - | if err != nil { | |||
| 145 | - | return err | |||
| 146 | - | } | |||
| 147 | - | ||||
| 148 | - | config.Targets[name] = remote.TargetEntry{ | |||
| 149 | - | URL: url, | |||
| 150 | - | TokenEnv: tokenEnv, | |||
| 151 | - | } | |||
| 152 | - | ||||
| 153 | - | return remote.SaveTargets("", config) | |||
| 154 | - | } | |||
| 155 | - | ||||
| 156 | - | // newTargetsRemoveCmd creates the targets remove command. | |||
| 157 | - | func newTargetsRemoveCmd() *cobra.Command { | |||
| 158 | - | var force bool | |||
| 159 | - | ||||
| 160 | - | cmd := &cobra.Command{ | |||
| 161 | - | Use: "remove <name>", | |||
| 162 | - | Aliases: []string{"rm"}, | |||
| 163 | - | Short: "Remove a target", | |||
| 164 | - | Args: cobra.ExactArgs(1), | |||
| 165 | - | RunE: func(cmd *cobra.Command, args []string) error { | |||
| 166 | - | name := args[0] | |||
| 167 | - | ||||
| 168 | - | // Check if target exists | |||
| 169 | - | targets, active, err := remote.ListTargets() | |||
| 170 | - | if err != nil { | |||
| 171 | - | return fmt.Errorf("failed to list targets: %w", err) | |||
| 172 | - | } | |||
| 173 | - | ||||
| 174 | - | if _, exists := targets[name]; !exists { | |||
| 175 | - | fmt.Println(styles.RenderError(fmt.Sprintf("Target '%s' not found", name))) | |||
| 176 | - | return nil | |||
| 177 | - | } | |||
| 178 | - | ||||
| 179 | - | // Confirm unless --force | |||
| 180 | - | if !force { | |||
| 181 | - | message := fmt.Sprintf("Remove target '%s'?", name) | |||
| 182 | - | if name == active { | |||
| 183 | - | message = fmt.Sprintf("Remove target '%s'? (currently active)", name) | |||
| 184 | - | } | |||
| 185 | - | ||||
| 186 | - | confirmed, err := components.RunConfirm(message) | |||
| 187 | - | if err != nil { | |||
| 188 | - | return err | |||
| 189 | - | } | |||
| 190 | - | if !confirmed { | |||
| 191 | - | fmt.Println(styles.Theme.Muted.Render("Cancelled")) | |||
| 192 | - | return nil | |||
| 193 | - | } | |||
| 194 | - | } | |||
| 195 | - | ||||
| 196 | - | if err := remote.RemoveTarget(name); err != nil { | |||
| 197 | - | return fmt.Errorf("failed to remove target: %w", err) | |||
| 198 | - | } | |||
| 199 | - | ||||
| 200 | - | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Target removed: %s", name))) | |||
| 201 | - | return nil | |||
| 202 | - | }, | |||
| 203 | - | } | |||
| 204 | - | ||||
| 205 | - | cmd.Flags().BoolVarP(&force, "force", "f", false, "Skip confirmation") | |||
| 206 | - | ||||
| 207 | - | return cmd | |||
| 208 | - | } | |||
| 209 | - | ||||
| 210 | - | // newTargetsUseCmd creates the targets use command. | |||
| 211 | - | func newTargetsUseCmd() *cobra.Command { | |||
| 212 | - | return &cobra.Command{ | |||
| 213 | - | Use: "use <name>", | |||
| 214 | - | Short: "Set the active target", | |||
| 215 | - | Long: `Set a target as the active default. | |||
| 216 | - | ||||
| 217 | - | When a target is active, it will be used automatically for remote commands | |||
| 218 | - | without needing to specify --target. | |||
| 219 | - | ||||
| 220 | - | Examples: | |||
| 221 | - | gordon targets use prod | |||
| 222 | - | gordon routes list # Uses prod target automatically`, | |||
| 223 | - | Args: cobra.ExactArgs(1), | |||
| 224 | - | RunE: func(cmd *cobra.Command, args []string) error { | |||
| 225 | - | name := args[0] | |||
| 226 | - | ||||
| 227 | - | if err := remote.SetActiveTarget(name); err != nil { | |||
| 228 | - | if err.Error() == fmt.Sprintf("target '%s' not found", name) { | |||
| 229 | - | fmt.Println(styles.RenderError(fmt.Sprintf("Target '%s' not found", name))) | |||
| 230 | - | fmt.Println() | |||
| 231 | - | fmt.Println("Available targets:") | |||
| 232 | - | ||||
| 233 | - | targets, _, _ := remote.ListTargets() | |||
| 234 | - | for targetName := range targets { | |||
| 235 | - | fmt.Printf(" %s\n", targetName) | |||
| 236 | - | } | |||
| 237 | - | return nil | |||
| 238 | - | } | |||
| 239 | - | return fmt.Errorf("failed to set active target: %w", err) | |||
| 240 | - | } | |||
| 241 | - | ||||
| 242 | - | fmt.Println(styles.RenderSuccess(fmt.Sprintf("Active target set to '%s'", name))) | |||
| 243 | - | return nil | |||
| 244 | - | }, | |||
| 245 | - | } | |||
| 246 | - | } | |||