diff --git a/shortcuts/issue/issue.go b/shortcuts/issue/issue.go index 3a7a762..4264686 100644 --- a/shortcuts/issue/issue.go +++ b/shortcuts/issue/issue.go @@ -27,6 +27,9 @@ func Shortcuts() []*common.Shortcut { newBatchAssignShortcut(), newBatchLabelShortcut(), newBatchCreateShortcut(), + newLabelAddShortcut(), + newLabelRemoveShortcut(), + newLabelListShortcut(), { Name: "list", Description: "List issues", @@ -154,6 +157,37 @@ func Shortcuts() []*common.Shortcut { return ctx.Output(env) }, }, + { + Name: "reopen", + Description: "Reopen a closed issue", + Flags: []common.Flag{ + {Name: "number", Short: "n", Usage: "Issue number (as shown in the web URL)", Required: true}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + number, err := ctx.RequireArg("number") + if err != nil { + return err + } + current, err := fetchExistingIssue(ctx, number) + if err != nil { + return err + } + + body := map[string]interface{}{ + "subject": current.Subject, + "description": current.Description, + "status_id": 1, // 1 = open + } + env, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), body) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, { Name: "update", Description: "Update an issue", diff --git a/shortcuts/issue/label.go b/shortcuts/issue/label.go new file mode 100644 index 0000000..7b2e063 --- /dev/null +++ b/shortcuts/issue/label.go @@ -0,0 +1,77 @@ +package issue + +import ( + "fmt" + + "github.com/gitlink-org/gitlink-cli/shortcuts/common" +) + +func newLabelAddShortcut() *common.Shortcut { + return &common.Shortcut{ + Name: "label-add", + Description: "Add labels to an issue", + Flags: []common.Flag{ + {Name: "number", Short: "n", Usage: "Issue number", Required: true}, + {Name: "labels", Short: "l", Usage: "Comma-separated label names or IDs", Required: true}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + number, _ := ctx.RequireArg("number") + labelsStr, _ := ctx.RequireArg("labels") + body := map[string]interface{}{ + "labels": labelsStr, + } + env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/issues/%s/labels", v1RepoPath(ctx), number), body) + if err != nil { + return err + } + return ctx.Output(env) + }, + } +} + +func newLabelRemoveShortcut() *common.Shortcut { + return &common.Shortcut{ + Name: "label-remove", + Description: "Remove a label from an issue", + Flags: []common.Flag{ + {Name: "number", Short: "n", Usage: "Issue number", Required: true}, + {Name: "label", Short: "l", Usage: "Label ID to remove", Required: true}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + number, _ := ctx.RequireArg("number") + label, _ := ctx.RequireArg("label") + env, err := ctx.CallAPI("DELETE", fmt.Sprintf("%s/issues/%s/labels/%s", v1RepoPath(ctx), number, label), nil) + if err != nil { + return err + } + return ctx.Output(env) + }, + } +} + +func newLabelListShortcut() *common.Shortcut { + return &common.Shortcut{ + Name: "label-list", + Description: "List labels on an issue", + Flags: []common.Flag{ + {Name: "number", Short: "n", Usage: "Issue number", Required: true}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + number, _ := ctx.RequireArg("number") + env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/issues/%s/labels", v1RepoPath(ctx), number), nil) + if err != nil { + return err + } + return ctx.Output(env) + }, + } +} diff --git a/shortcuts/milestone/milestone.go b/shortcuts/milestone/milestone.go new file mode 100644 index 0000000..7062a0e --- /dev/null +++ b/shortcuts/milestone/milestone.go @@ -0,0 +1,115 @@ +package milestone + +import ( + "fmt" + "net/url" + + "github.com/gitlink-org/gitlink-cli/shortcuts/common" +) + +func Shortcuts() []*common.Shortcut { + return []*common.Shortcut{ + { + Name: "list", + Description: "List milestones", + Flags: []common.Flag{ + {Name: "page", Short: "p", Usage: "Page number", Default: "1"}, + {Name: "limit", Short: "l", Usage: "Items per page", Default: "20"}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + q := url.Values{} + q.Set("page", ctx.Arg("page")) + q.Set("limit", ctx.Arg("limit")) + env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/v1%s/milestones", ctx.RepoPath()), q) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + { + Name: "create", + Description: "Create a milestone", + Flags: []common.Flag{ + {Name: "name", Short: "n", Usage: "Milestone name", Required: true}, + {Name: "description", Short: "d", Usage: "Description"}, + {Name: "due", Usage: "Due date (YYYY-MM-DD)"}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + name, _ := ctx.RequireArg("name") + body := map[string]interface{}{ + "title": name, + } + if d := ctx.Arg("description"); d != "" { + body["description"] = d + } + if due := ctx.Arg("due"); due != "" { + body["due_date"] = due + } + env, err := ctx.CallAPI("POST", fmt.Sprintf("/v1%s/milestones", ctx.RepoPath()), body) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + { + Name: "update", + Description: "Update a milestone", + Flags: []common.Flag{ + {Name: "id", Short: "i", Usage: "Milestone ID", Required: true}, + {Name: "name", Short: "n", Usage: "New name"}, + {Name: "description", Short: "d", Usage: "New description"}, + {Name: "due", Usage: "New due date (YYYY-MM-DD)"}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + id, _ := ctx.RequireArg("id") + body := map[string]interface{}{} + if n := ctx.Arg("name"); n != "" { + body["title"] = n + } + if d := ctx.Arg("description"); d != "" { + body["description"] = d + } + if due := ctx.Arg("due"); due != "" { + body["due_date"] = due + } + if len(body) == 0 { + return fmt.Errorf("at least one of --name, --description, --due is required") + } + env, err := ctx.CallAPI("PATCH", fmt.Sprintf("/v1%s/milestones/%s", ctx.RepoPath(), id), body) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + { + Name: "delete", + Description: "Delete a milestone", + Flags: []common.Flag{ + {Name: "id", Short: "i", Usage: "Milestone ID", Required: true}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + id, _ := ctx.RequireArg("id") + env, err := ctx.CallAPI("DELETE", fmt.Sprintf("/v1%s/milestones/%s", ctx.RepoPath(), id), nil) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + } +} diff --git a/shortcuts/org/org.go b/shortcuts/org/org.go index d58f24d..6719e40 100644 --- a/shortcuts/org/org.go +++ b/shortcuts/org/org.go @@ -91,6 +91,48 @@ func Shortcuts() []*common.Shortcut { return ctx.Output(env) }, }, + { + Name: "update", + Description: "Update an organization", + Flags: []common.Flag{ + {Name: "id", Short: "i", Usage: "Organization ID", Required: true}, + {Name: "name", Short: "n", Usage: "New name"}, + {Name: "description", Short: "d", Usage: "New description"}, + }, + Run: func(ctx *common.RuntimeContext) error { + id, _ := ctx.RequireArg("id") + body := map[string]interface{}{} + if n := ctx.Arg("name"); n != "" { + body["name"] = n + } + if d := ctx.Arg("description"); d != "" { + body["description"] = d + } + if len(body) == 0 { + return fmt.Errorf("at least one of --name, --description is required") + } + env, err := ctx.CallAPI("PATCH", fmt.Sprintf("/organizations/%s", id), body) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + { + Name: "delete", + Description: "Delete an organization", + Flags: []common.Flag{ + {Name: "id", Short: "i", Usage: "Organization ID", Required: true}, + }, + Run: func(ctx *common.RuntimeContext) error { + id, _ := ctx.RequireArg("id") + env, err := ctx.CallAPI("DELETE", fmt.Sprintf("/organizations/%s", id), nil) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, { Name: "invite", Description: "Invite a member to a project", diff --git a/shortcuts/pr/pr.go b/shortcuts/pr/pr.go index 13898b5..7a55243 100644 --- a/shortcuts/pr/pr.go +++ b/shortcuts/pr/pr.go @@ -10,6 +10,9 @@ import ( func Shortcuts() []*common.Shortcut { return []*common.Shortcut{ + newApproveShortcut(), + newRequestChangesShortcut(), + newReviewsShortcut(), { Name: "list", Description: "List pull requests", diff --git a/shortcuts/pr/review.go b/shortcuts/pr/review.go new file mode 100644 index 0000000..72c1cfb --- /dev/null +++ b/shortcuts/pr/review.go @@ -0,0 +1,83 @@ +package pr + +import ( + "fmt" + + "github.com/gitlink-org/gitlink-cli/shortcuts/common" +) + +func newApproveShortcut() *common.Shortcut { + return &common.Shortcut{ + Name: "approve", + Description: "Approve a pull request", + Flags: []common.Flag{ + {Name: "id", Short: "i", Usage: "PR number", Required: true}, + {Name: "body", Short: "b", Usage: "Review comment (optional)"}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + id, _ := ctx.RequireArg("id") + body := map[string]interface{}{ + "state": "approved", + } + if b := ctx.Arg("body"); b != "" { + body["body"] = b + } + env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/pulls/%s/reviews", ctx.RepoPath(), id), body) + if err != nil { + return err + } + return ctx.Output(env) + }, + } +} + +func newRequestChangesShortcut() *common.Shortcut { + return &common.Shortcut{ + Name: "request-changes", + Description: "Request changes on a pull request", + Flags: []common.Flag{ + {Name: "id", Short: "i", Usage: "PR number", Required: true}, + {Name: "body", Short: "b", Usage: "Review comment explaining what needs to change", Required: true}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + id, _ := ctx.RequireArg("id") + body, _ := ctx.RequireArg("body") + payload := map[string]interface{}{ + "state": "changes_requested", + "body": body, + } + env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/pulls/%s/reviews", ctx.RepoPath(), id), payload) + if err != nil { + return err + } + return ctx.Output(env) + }, + } +} + +func newReviewsShortcut() *common.Shortcut { + return &common.Shortcut{ + Name: "reviews", + Description: "List reviews for a pull request", + Flags: []common.Flag{ + {Name: "id", Short: "i", Usage: "PR number", Required: true}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + id, _ := ctx.RequireArg("id") + env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/pulls/%s/reviews", ctx.RepoPath(), id), nil) + if err != nil { + return err + } + return ctx.Output(env) + }, + } +} diff --git a/shortcuts/register.go b/shortcuts/register.go index d511219..cd5515b 100644 --- a/shortcuts/register.go +++ b/shortcuts/register.go @@ -7,41 +7,50 @@ import ( "github.com/gitlink-org/gitlink-cli/shortcuts/ci" "github.com/gitlink-org/gitlink-cli/shortcuts/common" "github.com/gitlink-org/gitlink-cli/shortcuts/issue" + "github.com/gitlink-org/gitlink-cli/shortcuts/milestone" "github.com/gitlink-org/gitlink-cli/shortcuts/org" "github.com/gitlink-org/gitlink-cli/shortcuts/pr" "github.com/gitlink-org/gitlink-cli/shortcuts/release" "github.com/gitlink-org/gitlink-cli/shortcuts/repo" "github.com/gitlink-org/gitlink-cli/shortcuts/search" + "github.com/gitlink-org/gitlink-cli/shortcuts/team" "github.com/gitlink-org/gitlink-cli/shortcuts/user" - "github.com/gitlink-org/gitlink-cli/shortcuts/wiki" // 新增wiki管理 + "github.com/gitlink-org/gitlink-cli/shortcuts/webhook" + "github.com/gitlink-org/gitlink-cli/shortcuts/wiki" ) // RegisterAll mounts all shortcut groups onto the root command. func RegisterAll(root *cobra.Command) { groups := map[string][]*common.Shortcut{ - "repo": repo.Shortcuts(), - "issue": issue.Shortcuts(), - "pr": pr.Shortcuts(), - "release": release.Shortcuts(), - "branch": branch.Shortcuts(), - "org": org.Shortcuts(), - "user": user.Shortcuts(), - "search": search.Shortcuts(), - "ci": ci.Shortcuts(), - "wiki": wiki.Shortcuts(), // 新增wiki + "repo": repo.Shortcuts(), + "issue": issue.Shortcuts(), + "pr": pr.Shortcuts(), + "release": release.Shortcuts(), + "branch": branch.Shortcuts(), + "org": org.Shortcuts(), + "user": user.Shortcuts(), + "search": search.Shortcuts(), + "ci": ci.Shortcuts(), + "milestone": milestone.Shortcuts(), + "team": team.Shortcuts(), + "wiki": wiki.Shortcuts(), + "webhook": webhook.Shortcuts(), } descriptions := map[string]string{ - "repo": "Repository operations", - "issue": "Issue operations", - "pr": "Pull request operations", - "release": "Release operations", - "branch": "Branch operations", - "org": "Organization operations", - "user": "User operations", - "search": "Search operations", - "ci": "CI/CD operations", - "wiki": "Wiki operations", // 新增wiki + "repo": "Repository operations", + "issue": "Issue operations", + "pr": "Pull request operations", + "release": "Release operations", + "branch": "Branch operations", + "org": "Organization operations", + "user": "User operations", + "search": "Search operations", + "ci": "CI/CD operations", + "milestone": "Milestone operations", + "team": "Team operations", + "wiki": "Wiki operations", + "webhook": "Webhook operations", } for name, shortcuts := range groups { diff --git a/shortcuts/release/release.go b/shortcuts/release/release.go index bbbb76c..07893ec 100644 --- a/shortcuts/release/release.go +++ b/shortcuts/release/release.go @@ -109,16 +109,12 @@ func Shortcuts() []*common.Shortcut { id, _ := ctx.RequireArg("id") _, delErr := ctx.CallAPI("DELETE", fmt.Sprintf("%s/releases/%s", ctx.RepoPath(), id), nil) if delErr != nil { - // GitLink API bug: delete succeeds but returns error status. - // Verify by checking if the release still exists. _, viewErr := ctx.CallAPI("GET", fmt.Sprintf("%s/releases/%s", ctx.RepoPath(), id), nil) if viewErr != nil { - // Release no longer exists — delete actually succeeded return ctx.Output(output.SuccessEnvelope(map[string]interface{}{ "message": "删除成功", }, nil)) } - // Release still exists — delete truly failed return delErr } return ctx.Output(output.SuccessEnvelope(map[string]interface{}{ @@ -126,5 +122,39 @@ func Shortcuts() []*common.Shortcut { }, nil)) }, }, + { + Name: "update", + Description: "Update a release", + Flags: []common.Flag{ + {Name: "id", Short: "i", Usage: "Release ID", Required: true}, + {Name: "name", Short: "n", Usage: "New release name"}, + {Name: "body", Short: "b", Usage: "New release notes"}, + {Name: "prerelease", Usage: "Mark as prerelease (true/false)"}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + id, _ := ctx.RequireArg("id") + body := map[string]interface{}{} + if n := ctx.Arg("name"); n != "" { + body["name"] = n + } + if b := ctx.Arg("body"); b != "" { + body["body"] = b + } + if p := ctx.Arg("prerelease"); p != "" { + body["prerelease"] = p == "true" + } + if len(body) == 0 { + return fmt.Errorf("at least one of --name, --body, --prerelease is required") + } + env, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/releases/%s", ctx.RepoPath(), id), body) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, } } diff --git a/shortcuts/repo/repo.go b/shortcuts/repo/repo.go index be6cbad..b448996 100644 --- a/shortcuts/repo/repo.go +++ b/shortcuts/repo/repo.go @@ -142,6 +142,34 @@ func Shortcuts() []*common.Shortcut { return ctx.Output(env) }, }, + { + Name: "update", + Description: "Update repository settings", + Flags: []common.Flag{ + {Name: "description", Short: "d", Usage: "New description"}, + {Name: "private", Usage: "Set private (true/false)"}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + body := map[string]interface{}{} + if d := ctx.Arg("description"); d != "" { + body["description"] = d + } + if p := ctx.Arg("private"); p != "" { + body["private"] = p == "true" + } + if len(body) == 0 { + return fmt.Errorf("at least one of --description, --private is required") + } + env, err := ctx.CallAPI("PATCH", ctx.RepoPath(), body) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, { Name: "members", Description: "List repository members (collaborators)", diff --git a/shortcuts/search/search.go b/shortcuts/search/search.go index 443dec3..1c7b89a 100644 --- a/shortcuts/search/search.go +++ b/shortcuts/search/search.go @@ -1,6 +1,7 @@ package search import ( + "fmt" "net/url" "github.com/gitlink-org/gitlink-cli/shortcuts/common" @@ -50,5 +51,29 @@ func Shortcuts() []*common.Shortcut { return ctx.Output(env) }, }, + { + Name: "issues", + Description: "Search issues", + Flags: []common.Flag{ + {Name: "keyword", Short: "k", Usage: "Search keyword", Required: true}, + {Name: "page", Short: "p", Usage: "Page number", Default: "1"}, + {Name: "limit", Short: "l", Usage: "Items per page", Default: "20"}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + keyword, _ := ctx.RequireArg("keyword") + q := url.Values{} + q.Set("search", keyword) + q.Set("page", ctx.Arg("page")) + q.Set("limit", ctx.Arg("limit")) + env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/v1%s/issues", ctx.RepoPath()), q) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, } } diff --git a/shortcuts/team/team.go b/shortcuts/team/team.go new file mode 100644 index 0000000..87e1ff7 --- /dev/null +++ b/shortcuts/team/team.go @@ -0,0 +1,137 @@ +package team + +import ( + "fmt" + "net/url" + + "github.com/gitlink-org/gitlink-cli/shortcuts/common" +) + +func Shortcuts() []*common.Shortcut { + return []*common.Shortcut{ + { + Name: "list", + Description: "List teams in an organization", + Flags: []common.Flag{ + {Name: "org", Short: "o", Usage: "Organization ID or login", Required: true}, + {Name: "page", Short: "p", Usage: "Page number", Default: "1"}, + {Name: "limit", Short: "l", Usage: "Items per page", Default: "20"}, + }, + Run: func(ctx *common.RuntimeContext) error { + org, _ := ctx.RequireArg("org") + q := url.Values{} + q.Set("page", ctx.Arg("page")) + q.Set("limit", ctx.Arg("limit")) + env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/organizations/%s/teams", org), q) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + { + Name: "create", + Description: "Create a team in an organization", + Flags: []common.Flag{ + {Name: "org", Short: "o", Usage: "Organization ID or login", Required: true}, + {Name: "name", Short: "n", Usage: "Team name", Required: true}, + {Name: "description", Short: "d", Usage: "Team description"}, + }, + Run: func(ctx *common.RuntimeContext) error { + org, _ := ctx.RequireArg("org") + name, _ := ctx.RequireArg("name") + body := map[string]interface{}{ + "name": name, + } + if desc := ctx.Arg("description"); desc != "" { + body["description"] = desc + } + env, err := ctx.CallAPI("POST", fmt.Sprintf("/organizations/%s/teams", org), body) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + { + Name: "delete", + Description: "Delete a team", + Flags: []common.Flag{ + {Name: "org", Short: "o", Usage: "Organization ID or login", Required: true}, + {Name: "id", Short: "i", Usage: "Team ID", Required: true}, + }, + Run: func(ctx *common.RuntimeContext) error { + org, _ := ctx.RequireArg("org") + id, _ := ctx.RequireArg("id") + env, err := ctx.CallAPI("DELETE", fmt.Sprintf("/organizations/%s/teams/%s", org, id), nil) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + { + Name: "members", + Description: "List members of a team", + Flags: []common.Flag{ + {Name: "org", Short: "o", Usage: "Organization ID or login", Required: true}, + {Name: "id", Short: "i", Usage: "Team ID", Required: true}, + {Name: "page", Short: "p", Usage: "Page number", Default: "1"}, + {Name: "limit", Short: "l", Usage: "Items per page", Default: "20"}, + }, + Run: func(ctx *common.RuntimeContext) error { + org, _ := ctx.RequireArg("org") + id, _ := ctx.RequireArg("id") + q := url.Values{} + q.Set("page", ctx.Arg("page")) + q.Set("limit", ctx.Arg("limit")) + env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/organizations/%s/teams/%s/members", org, id), q) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + { + Name: "member-add", + Description: "Add a user to a team", + Flags: []common.Flag{ + {Name: "org", Short: "o", Usage: "Organization ID or login", Required: true}, + {Name: "team", Short: "t", Usage: "Team ID", Required: true}, + {Name: "user", Short: "u", Usage: "User login or ID to add", Required: true}, + }, + Run: func(ctx *common.RuntimeContext) error { + org, _ := ctx.RequireArg("org") + team, _ := ctx.RequireArg("team") + user, _ := ctx.RequireArg("user") + body := map[string]interface{}{ + "user_id": user, + } + env, err := ctx.CallAPI("POST", fmt.Sprintf("/organizations/%s/teams/%s/members", org, team), body) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + { + Name: "member-remove", + Description: "Remove a user from a team", + Flags: []common.Flag{ + {Name: "org", Short: "o", Usage: "Organization ID or login", Required: true}, + {Name: "team", Short: "t", Usage: "Team ID", Required: true}, + {Name: "user", Short: "u", Usage: "User login or ID to remove", Required: true}, + }, + Run: func(ctx *common.RuntimeContext) error { + org, _ := ctx.RequireArg("org") + team, _ := ctx.RequireArg("team") + user, _ := ctx.RequireArg("user") + env, err := ctx.CallAPI("DELETE", fmt.Sprintf("/organizations/%s/teams/%s/members/%s", org, team, user), nil) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + } +}