From f458d891964a08da63a949eea1f631c397904399 Mon Sep 17 00:00:00 2001 From: Guy Ben-Aharon Date: Thu, 21 May 2026 14:02:15 +0300 Subject: [PATCH 1/2] feat: add disconnect api endpoints (#67) --- cmd/onecli/apps.go | 39 ++++++++++++++++++++++++++++++++------- internal/api/apps.go | 41 +++++++++++++++++++++++++++++++++-------- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/cmd/onecli/apps.go b/cmd/onecli/apps.go index 189de7b..5d7be31 100644 --- a/cmd/onecli/apps.go +++ b/cmd/onecli/apps.go @@ -160,23 +160,48 @@ func (c *AppsRemoveCmd) Run(out *output.Writer) error { // AppsDisconnectCmd is `onecli apps disconnect`. type AppsDisconnectCmd struct { - Provider string `required:"" help:"Provider name (e.g. 'github', 'gmail')."` - DryRun bool `optional:"" name:"dry-run" help:"Validate the request without executing it."` + Provider string `required:"" help:"Provider name (e.g. 'github', 'gmail')."` + ConnectionID string `optional:"" name:"connection-id" help:"Connection ID to disconnect (required if multiple connections exist)."` + DryRun bool `optional:"" name:"dry-run" help:"Validate the request without executing it."` } func (c *AppsDisconnectCmd) Run(out *output.Writer) error { if err := validate.ResourceID(c.Provider); err != nil { return fmt.Errorf("invalid provider: %w", err) } - if c.DryRun { - return out.WriteDryRun("Would disconnect app", map[string]string{"provider": c.Provider}) - } client, err := newClient() if err != nil { return err } - if err := client.DisconnectApp(newContext(), c.Provider); err != nil { + + connectionID := c.ConnectionID + if connectionID == "" { + connections, err := client.ListConnectionsByProvider(newContext(), c.Provider) + if err != nil { + return err + } + if len(connections) == 0 { + return fmt.Errorf("no connections found for %s", c.Provider) + } + if len(connections) > 1 { + out.Stderr(fmt.Sprintf("Multiple connections found for %s:", c.Provider)) + for _, conn := range connections { + label := conn.Label + if label == "" { + label = conn.ID + } + out.Stderr(fmt.Sprintf(" %s %s (%s)", conn.ID, label, conn.Status)) + } + return fmt.Errorf("specify --connection-id to disconnect a specific connection") + } + connectionID = connections[0].ID + } + + if c.DryRun { + return out.WriteDryRun("Would disconnect app", map[string]string{"provider": c.Provider, "connectionId": connectionID}) + } + if err := client.DisconnectApp(newContext(), connectionID); err != nil { return err } - return out.Write(map[string]string{"status": "disconnected", "provider": c.Provider}) + return out.Write(map[string]string{"status": "disconnected", "provider": c.Provider, "connectionId": connectionID}) } diff --git a/internal/api/apps.go b/internal/api/apps.go index ed817ac..21da3e9 100644 --- a/internal/api/apps.go +++ b/internal/api/apps.go @@ -26,6 +26,9 @@ type AppConfig struct { // AppConnection is the OAuth connection status. type AppConnection struct { + ID string `json:"id"` + Provider string `json:"provider"` + Label string `json:"label,omitempty"` Status string `json:"status"` Scopes []string `json:"scopes"` ConnectedAt string `json:"connectedAt"` @@ -55,6 +58,36 @@ func (c *Client) GetApp(ctx context.Context, provider string) (*App, error) { return &app, nil } +// ListConnections returns all app connections for the current project. +func (c *Client) ListConnections(ctx context.Context) ([]AppConnection, error) { + var resp struct { + Connections []AppConnection `json:"connections"` + } + if err := c.do(ctx, http.MethodGet, "/v1/apps/connections", nil, &resp); err != nil { + return nil, fmt.Errorf("listing connections: %w", err) + } + return resp.Connections, nil +} + +// ListConnectionsByProvider returns app connections for a specific provider. +func (c *Client) ListConnectionsByProvider(ctx context.Context, provider string) ([]AppConnection, error) { + var resp struct { + Connections []AppConnection `json:"connections"` + } + if err := c.do(ctx, http.MethodGet, "/v1/apps/connections/"+provider, nil, &resp); err != nil { + return nil, fmt.Errorf("listing connections for %s: %w", provider, err) + } + return resp.Connections, nil +} + +// DisconnectApp removes an app connection by ID. +func (c *Client) DisconnectApp(ctx context.Context, connectionID string) error { + if err := c.do(ctx, http.MethodDelete, "/v1/apps/connections/"+connectionID, nil, nil); err != nil { + return fmt.Errorf("disconnecting app: %w", err) + } + return nil +} + // ConfigureApp saves BYOC credentials for a provider. func (c *Client) ConfigureApp(ctx context.Context, provider string, input ConfigAppInput) error { var resp SuccessResponse @@ -71,11 +104,3 @@ func (c *Client) UnconfigureApp(ctx context.Context, provider string) error { } return nil } - -// DisconnectApp removes the OAuth connection for a provider. -func (c *Client) DisconnectApp(ctx context.Context, provider string) error { - if err := c.do(ctx, http.MethodDelete, "/v1/apps/"+provider+"/connection", nil, nil); err != nil { - return fmt.Errorf("disconnecting app: %w", err) - } - return nil -} From 57d856d4574f4b2211972495650a93ea6a254c09 Mon Sep 17 00:00:00 2001 From: Guy Ben-Aharon Date: Thu, 21 May 2026 14:03:45 +0300 Subject: [PATCH 2/2] chore(main): release 2.2.0 (#68) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 119484d..6410e57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [2.2.0](https://github.com/onecli/onecli-cli/compare/v2.1.0...v2.2.0) (2026-05-21) + + +### Features + +* add disconnect api endpoints ([#67](https://github.com/onecli/onecli-cli/issues/67)) ([f458d89](https://github.com/onecli/onecli-cli/commit/f458d891964a08da63a949eea1f631c397904399)) + ## [2.1.0](https://github.com/onecli/onecli-cli/compare/v2.0.1...v2.1.0) (2026-05-20)