diff --git a/.surface b/.surface index 372852d3..1f6f7ff8 100644 --- a/.surface +++ b/.surface @@ -3479,6 +3479,7 @@ FLAG basecamp checkin answer update --verbose type=count FLAG basecamp checkin answers --account type=string FLAG basecamp checkin answers --agent type=bool FLAG basecamp checkin answers --all type=bool +FLAG basecamp checkin answers --by type=string FLAG basecamp checkin answers --cache-dir type=string FLAG basecamp checkin answers --count type=bool FLAG basecamp checkin answers --help type=bool @@ -3747,6 +3748,7 @@ FLAG basecamp checkins answer update --verbose type=count FLAG basecamp checkins answers --account type=string FLAG basecamp checkins answers --agent type=bool FLAG basecamp checkins answers --all type=bool +FLAG basecamp checkins answers --by type=string FLAG basecamp checkins answers --cache-dir type=string FLAG basecamp checkins answers --count type=bool FLAG basecamp checkins answers --help type=bool diff --git a/internal/commands/checkins.go b/internal/commands/checkins.go index bbfa3e2a..a3fbe06c 100644 --- a/internal/commands/checkins.go +++ b/internal/commands/checkins.go @@ -528,6 +528,7 @@ func newCheckinsAnswersCmd(project *string) *cobra.Command { var limit int var page int var all bool + var by string cmd := &cobra.Command{ Use: "answers ", @@ -536,7 +537,11 @@ func newCheckinsAnswersCmd(project *string) *cobra.Command { You can pass either a question ID or a Basecamp URL: basecamp checkins answers 789 --in my-project - basecamp checkins answers https://3.basecamp.com/123/buckets/456/questions/789`, + basecamp checkins answers https://3.basecamp.com/123/buckets/456/questions/789 + +Use --by to filter answers by a specific person (name, email, ID, or "me"): + basecamp checkins answers 789 --by me --in my-project + basecamp checkins answers 789 --by "Alice Smith" --in my-project`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { app := appctx.FromContext(cmd.Context()) @@ -598,11 +603,33 @@ You can pass either a question ID or a Basecamp URL: opts.Page = page } - answersResult, err := app.Account().Checkins().ListAnswers(cmd.Context(), questionID, opts) - if err != nil { - return convertSDKError(err) + trimmedBy := strings.TrimSpace(by) + if by != "" && trimmedBy == "" { + return output.ErrUsage("--by value cannot be blank") + } + + var answers []basecamp.QuestionAnswer + if trimmedBy != "" { + personIDStr, _, err := app.Names.ResolvePerson(cmd.Context(), trimmedBy) + if err != nil { + return err + } + personID, err := strconv.ParseInt(personIDStr, 10, 64) + if err != nil { + return output.ErrUsage("Invalid person ID") + } + answersResult, err := app.Account().Checkins().ListAnswersByUser(cmd.Context(), questionID, personID, opts) + if err != nil { + return convertSDKError(err) + } + answers = answersResult.Answers + } else { + answersResult, err := app.Account().Checkins().ListAnswers(cmd.Context(), questionID, opts) + if err != nil { + return convertSDKError(err) + } + answers = answersResult.Answers } - answers := answersResult.Answers return app.OK(answers, output.WithSummary(fmt.Sprintf("%d answers", len(answers))), @@ -625,6 +652,7 @@ You can pass either a question ID or a Basecamp URL: cmd.Flags().IntVarP(&limit, "limit", "n", 0, "Maximum number of answers to fetch (0 = all)") cmd.Flags().BoolVar(&all, "all", false, "Fetch all answers (no limit)") cmd.Flags().IntVar(&page, "page", 0, "Fetch a single page (use --all for everything)") + cmd.Flags().StringVar(&by, "by", "", "Filter answers by person (name, email, ID, or \"me\")") return cmd } diff --git a/internal/commands/checkins_test.go b/internal/commands/checkins_test.go index 7194e7a2..82d5ef90 100644 --- a/internal/commands/checkins_test.go +++ b/internal/commands/checkins_test.go @@ -12,6 +12,66 @@ import ( "github.com/stretchr/testify/require" ) +type mockCheckinsAnswersByPersonTransport struct { + recordedPath string +} + +func (m *mockCheckinsAnswersByPersonTransport) RoundTrip(req *http.Request) (*http.Response, error) { + header := make(http.Header) + header.Set("Content-Type", "application/json") + + switch { + case req.Method == "GET" && strings.Contains(req.URL.Path, "/projects.json"): + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(strings.NewReader(`[{"id":123,"name":"Test Project"}]`)), + Header: header, + }, nil + case req.Method == "GET" && strings.Contains(req.URL.Path, "/people.json"): + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(strings.NewReader(`[{"id":456,"name":"Alice Smith","email_address":"alice@example.com"}]`)), + Header: header, + }, nil + case req.Method == "GET" && strings.Contains(req.URL.Path, "/questions/789/answers/by/456"): + m.recordedPath = req.URL.Path + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(strings.NewReader(`[{ + "id": 1001, + "content": "
Alice's answer
", + "group_on": "2026-04-21", + "creator": {"id": 456, "name": "Alice Smith"}, + "parent": {"id": 789, "title": "What did you work on?", "type": "Question", "url": "https://example.test/questions/789", "app_url": "https://example.test/questions/789"}, + "bucket": {"id": 123, "name": "Test Project", "type": "Project"}, + "status": "active", + "type": "Question::Answer", + "title": "What did you work on?" + }]`)), + Header: header, + }, nil + default: + return &http.Response{ + StatusCode: 404, + Body: io.NopCloser(strings.NewReader(`{"error":"Not Found"}`)), + Header: header, + }, nil + } +} + +func TestCheckinsAnswersByPersonFlag(t *testing.T) { + transport := &mockCheckinsAnswersByPersonTransport{} + app, _ := newTestAppWithTransport(t, transport) + app.Config.ProjectID = "123" + + project := "" + cmd := newCheckinsAnswersCmd(&project) + + err := executeCommand(cmd, app, "789", "--by", "Alice Smith") + require.NoError(t, err) + assert.Contains(t, transport.recordedPath, "/questions/789/answers/by/456") +} + type mockCheckinsAnswerCreateTransport struct { recordedPath string recordedBody map[string]any diff --git a/skills/basecamp/SKILL.md b/skills/basecamp/SKILL.md index b29ab263..549224b9 100644 --- a/skills/basecamp/SKILL.md +++ b/skills/basecamp/SKILL.md @@ -562,6 +562,8 @@ basecamp checkins --in --json # Questionnaire info basecamp checkins questions --in # List questions basecamp checkins question --in # Question details basecamp checkins answers --in # List answers +basecamp checkins answers --by me --in # My answers only +basecamp checkins answers --by "Alice Smith" --in # Filter by person (name, email, or ID) basecamp checkins answer --in # Answer details basecamp checkins question create "What did you work on?" --in basecamp checkins question update "New question" --frequency every_week