Skip to content

Commit 48698f0

Browse files
masnwilliamsclaude
andcommitted
add projects update command
Supports renaming and archiving/reactivating projects via --name and --status flags. Accepts ID or name as the positional argument. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent b67d5ee commit 48698f0

2 files changed

Lines changed: 128 additions & 0 deletions

File tree

‎cmd/projects.go‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type ProjectsService interface {
2222
ProjectListService
2323
New(ctx context.Context, body kernel.ProjectNewParams, opts ...option.RequestOption) (res *kernel.Project, err error)
2424
Get(ctx context.Context, id string, opts ...option.RequestOption) (res *kernel.Project, err error)
25+
Update(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (res *kernel.Project, err error)
2526
Delete(ctx context.Context, id string, opts ...option.RequestOption) (err error)
2627
}
2728

@@ -45,6 +46,14 @@ type ProjectsGetInput struct {
4546
Identifier string
4647
}
4748

49+
type ProjectsUpdateInput struct {
50+
Identifier string
51+
Name string
52+
NameSet bool
53+
Status string
54+
StatusSet bool
55+
}
56+
4857
type ProjectsDeleteInput struct {
4958
Identifier string
5059
}
@@ -133,6 +142,50 @@ func (c ProjectsCmd) Get(ctx context.Context, in ProjectsGetInput) error {
133142
return nil
134143
}
135144

145+
func (c ProjectsCmd) Update(ctx context.Context, in ProjectsUpdateInput) error {
146+
if !in.NameSet && !in.StatusSet {
147+
return fmt.Errorf("must provide at least one of --name or --status")
148+
}
149+
150+
projectID, err := resolveProjectArg(ctx, c.projects, in.Identifier)
151+
if err != nil {
152+
return err
153+
}
154+
155+
inner := kernel.UpdateProjectRequestParam{}
156+
if in.NameSet {
157+
inner.Name = param.NewOpt(in.Name)
158+
}
159+
if in.StatusSet {
160+
switch in.Status {
161+
case "active":
162+
inner.Status = kernel.UpdateProjectRequestStatusActive
163+
case "archived":
164+
inner.Status = kernel.UpdateProjectRequestStatusArchived
165+
default:
166+
return fmt.Errorf("--status must be one of: active, archived (got %q)", in.Status)
167+
}
168+
}
169+
170+
project, err := c.projects.Update(ctx, projectID, kernel.ProjectUpdateParams{
171+
UpdateProjectRequest: inner,
172+
})
173+
if err != nil {
174+
return util.CleanedUpSdkError{Err: err}
175+
}
176+
177+
pterm.Success.Printf("Updated project: %s (ID: %s)\n", project.Name, project.ID)
178+
table := pterm.TableData{
179+
{"Field", "Value"},
180+
{"ID", project.ID},
181+
{"Name", project.Name},
182+
{"Status", string(project.Status)},
183+
{"Updated At", util.FormatLocal(project.UpdatedAt)},
184+
}
185+
PrintTableNoPad(table, true)
186+
return nil
187+
}
188+
136189
func (c ProjectsCmd) Delete(ctx context.Context, in ProjectsDeleteInput) error {
137190
projectID, err := resolveProjectArg(ctx, c.projects, in.Identifier)
138191
if err != nil {
@@ -280,6 +333,19 @@ func runProjectsGet(cmd *cobra.Command, args []string) error {
280333
return c.Get(cmd.Context(), ProjectsGetInput{Identifier: args[0]})
281334
}
282335

336+
func runProjectsUpdate(cmd *cobra.Command, args []string) error {
337+
c := getProjectsHandler(cmd)
338+
name, _ := cmd.Flags().GetString("name")
339+
status, _ := cmd.Flags().GetString("status")
340+
return c.Update(cmd.Context(), ProjectsUpdateInput{
341+
Identifier: args[0],
342+
Name: name,
343+
NameSet: cmd.Flags().Changed("name"),
344+
Status: status,
345+
StatusSet: cmd.Flags().Changed("status"),
346+
})
347+
}
348+
283349
func runProjectsDelete(cmd *cobra.Command, args []string) error {
284350
c := getProjectsHandler(cmd)
285351
return c.Delete(cmd.Context(), ProjectsDeleteInput{Identifier: args[0]})
@@ -364,6 +430,13 @@ var projectsGetCmd = &cobra.Command{
364430
RunE: runProjectsGet,
365431
}
366432

433+
var projectsUpdateCmd = &cobra.Command{
434+
Use: "update <id-or-name>",
435+
Short: "Update a project (rename or change status)",
436+
Args: cobra.ExactArgs(1),
437+
RunE: runProjectsUpdate,
438+
}
439+
367440
var projectsDeleteCmd = &cobra.Command{
368441
Use: "delete <id-or-name>",
369442
Short: "Delete a project",
@@ -418,9 +491,13 @@ func init() {
418491
projectsLimitsCmd.AddCommand(projectsLimitsGetCmd)
419492
projectsLimitsCmd.AddCommand(projectsLimitsSetCmd)
420493

494+
projectsUpdateCmd.Flags().String("name", "", "New project name")
495+
projectsUpdateCmd.Flags().String("status", "", "New project status (active, archived)")
496+
421497
projectsCmd.AddCommand(projectsListCmd)
422498
projectsCmd.AddCommand(projectsCreateCmd)
423499
projectsCmd.AddCommand(projectsGetCmd)
500+
projectsCmd.AddCommand(projectsUpdateCmd)
424501
projectsCmd.AddCommand(projectsDeleteCmd)
425502
projectsCmd.AddCommand(projectsLimitsCmd)
426503
projectsCmd.AddCommand(projectsGetLimitsCompatCmd)

‎cmd/projects_test.go‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type FakeProjectsService struct {
4242
ListFunc func(ctx context.Context, query kernel.ProjectListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.Project], error)
4343
NewFunc func(ctx context.Context, body kernel.ProjectNewParams, opts ...option.RequestOption) (*kernel.Project, error)
4444
GetFunc func(ctx context.Context, id string, opts ...option.RequestOption) (*kernel.Project, error)
45+
UpdateFunc func(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error)
4546
DeleteFunc func(ctx context.Context, id string, opts ...option.RequestOption) error
4647
}
4748

@@ -66,6 +67,13 @@ func (f *FakeProjectsService) Get(ctx context.Context, id string, opts ...option
6667
return &kernel.Project{ID: id, Name: "default"}, nil
6768
}
6869

70+
func (f *FakeProjectsService) Update(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error) {
71+
if f.UpdateFunc != nil {
72+
return f.UpdateFunc(ctx, id, body, opts...)
73+
}
74+
return &kernel.Project{ID: id, Name: body.UpdateProjectRequest.Name.Value, Status: kernel.ProjectStatusActive}, nil
75+
}
76+
6977
func (f *FakeProjectsService) Delete(ctx context.Context, id string, opts ...option.RequestOption) error {
7078
if f.DeleteFunc != nil {
7179
return f.DeleteFunc(ctx, id, opts...)
@@ -92,6 +100,49 @@ func (f *FakeProjectLimitsService) Update(ctx context.Context, id string, body k
92100
return &kernel.ProjectLimits{}, nil
93101
}
94102

103+
func TestProjectsUpdate_RenamesAndUpdatesStatus(t *testing.T) {
104+
captureProjectsOutput(t)
105+
106+
var captured kernel.ProjectUpdateParams
107+
fakeProjects := &FakeProjectsService{
108+
UpdateFunc: func(ctx context.Context, id string, body kernel.ProjectUpdateParams, opts ...option.RequestOption) (*kernel.Project, error) {
109+
captured = body
110+
return &kernel.Project{ID: id, Name: "renamed", Status: kernel.ProjectStatusArchived}, nil
111+
},
112+
}
113+
c := ProjectsCmd{projects: fakeProjects, limits: &FakeProjectLimitsService{}}
114+
115+
err := c.Update(context.Background(), ProjectsUpdateInput{
116+
Identifier: "a12345678901234567890123",
117+
Name: "renamed",
118+
NameSet: true,
119+
Status: "archived",
120+
StatusSet: true,
121+
})
122+
assert.NoError(t, err)
123+
assert.True(t, captured.UpdateProjectRequest.Name.Valid())
124+
assert.Equal(t, "renamed", captured.UpdateProjectRequest.Name.Value)
125+
assert.Equal(t, kernel.UpdateProjectRequestStatusArchived, captured.UpdateProjectRequest.Status)
126+
}
127+
128+
func TestProjectsUpdate_RequiresAtLeastOneFlag(t *testing.T) {
129+
c := ProjectsCmd{projects: &FakeProjectsService{}, limits: &FakeProjectLimitsService{}}
130+
err := c.Update(context.Background(), ProjectsUpdateInput{Identifier: "a12345678901234567890123"})
131+
assert.Error(t, err)
132+
assert.Contains(t, err.Error(), "at least one of --name or --status")
133+
}
134+
135+
func TestProjectsUpdate_RejectsUnknownStatus(t *testing.T) {
136+
c := ProjectsCmd{projects: &FakeProjectsService{}, limits: &FakeProjectLimitsService{}}
137+
err := c.Update(context.Background(), ProjectsUpdateInput{
138+
Identifier: "a12345678901234567890123",
139+
Status: "deleted",
140+
StatusSet: true,
141+
})
142+
assert.Error(t, err)
143+
assert.Contains(t, err.Error(), "active, archived")
144+
}
145+
95146
func TestProjectsLimitsGet_DefaultOutput(t *testing.T) {
96147
buf := captureProjectsOutput(t)
97148
limits := &kernel.ProjectLimits{

0 commit comments

Comments
 (0)